diff --git a/.docker-mongo/Dockerfile b/.docker-mongo/Dockerfile index 56b809851faf..2e92d78bcb0a 100644 --- a/.docker-mongo/Dockerfile +++ b/.docker-mongo/Dockerfile @@ -1,4 +1,4 @@ -FROM node:14.18.2-bullseye-slim +FROM node:14.18.3-bullseye-slim LABEL maintainer="buildmaster@rocket.chat" diff --git a/.docker/Dockerfile b/.docker/Dockerfile index cfcc8b570936..f4c29bd338d0 100644 --- a/.docker/Dockerfile +++ b/.docker/Dockerfile @@ -1,4 +1,4 @@ -FROM node:14.18.2-bullseye-slim +FROM node:14.18.3-bullseye-slim LABEL maintainer="buildmaster@rocket.chat" diff --git a/.docker/Dockerfile.alpine b/.docker/Dockerfile.alpine index 5754077e1398..434715a1b4bd 100644 --- a/.docker/Dockerfile.alpine +++ b/.docker/Dockerfile.alpine @@ -4,19 +4,19 @@ RUN apk add --no-cache python3 make g++ libc6-compat ttf-dejavu ADD . /app -MAINTAINER buildmaster@rocket.chat +LABEL maintainer="buildmaster@rocket.chat" RUN set -x \ - && cd /app/bundle/programs/server \ - && npm install --production \ - # Start hack for sharp... - && rm -rf npm/node_modules/sharp \ - && npm install sharp@0.29.3 \ - && mv node_modules/sharp npm/node_modules/sharp \ - # End hack for sharp - && cd npm \ - && npm rebuild bcrypt --build-from-source \ - && npm cache clear --force + && cd /app/bundle/programs/server \ + && npm install --production \ + # Start hack for sharp... + && rm -rf npm/node_modules/sharp \ + && npm install sharp@0.29.3 \ + && mv node_modules/sharp npm/node_modules/sharp \ + # End hack for sharp + && cd npm \ + && npm rebuild bcrypt --build-from-source \ + && npm cache clear --force # needs a mongo instance - defaults to container linking with alias 'mongo' ENV DEPLOY_METHOD=docker \ diff --git a/.docker/Dockerfile.rhel b/.docker/Dockerfile.rhel index 4733db044237..9490420d455e 100644 --- a/.docker/Dockerfile.rhel +++ b/.docker/Dockerfile.rhel @@ -1,6 +1,6 @@ FROM registry.access.redhat.com/ubi8/nodejs-12 -ENV RC_VERSION 4.4.2 +ENV RC_VERSION 4.6.3 MAINTAINER buildmaster@rocket.chat diff --git a/.github/history.json b/.github/history.json index 7b5bb6a8d2c5..406ba05c9d1f 100644 --- a/.github/history.json +++ b/.github/history.json @@ -69993,10 +69993,2893 @@ ], "pull_requests": [] }, + "4.5.0-rc.0": { + "node_version": "14.18.3", + "npm_version": "6.14.15", + "apps_engine_version": "1.31.0-alpha.5979", + "mongo_versions": [ + "'3.6'", + "'4.0'", + "'4.2'", + "'4.4'", + "'5.0'" + ], + "pull_requests": [ + { + "pr": "24573", + "title": "Chore: Bump Fuselage packages", + "userLogin": "tassoevan", + "description": "It uses the last stable version of Fuselage packages.", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "24558", + "title": "i18n: Language update from LingoHub 🤖 on 2022-02-21Z", + "userLogin": "lingohub[bot]", + "contributors": [ + null, + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "24572", + "title": "[FIX] 2FA via email when logging in using OAuth", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24568", + "title": "Chore: Update Apps-Engine", + "userLogin": "d-gubert", + "milestone": "4.5.0", + "contributors": [ + "d-gubert", + "web-flow" + ] + }, + { + "pr": "24536", + "title": "Chore: roomTypes: Stop mixing client and server code together", + "userLogin": "pierre-lehnen-rc", + "milestone": "4.5.0", + "contributors": [ + "pierre-lehnen-rc", + "tassoevan", + "web-flow" + ] + }, + { + "pr": "24529", + "title": "[IMPROVE] Replace AutoComplete in UserAutoComplete & UserAutoCompleteMultiple components", + "userLogin": "juliajforesti", + "description": "This PR replaces a deprecated fuselage's component `AutoComplete` in favor of `Select` and `MultiSelect` which fixes some of UX/UI issues in selecting users\r\n\r\n### before\r\n![Screen Shot 2022-02-19 at 13 33 28](https://user-images.githubusercontent.com/27704687/154809737-8181a06c-4f20-48ea-90f7-01e828b9a452.png)\r\n\r\n### after\r\n![Screen Shot 2022-02-19 at 13 30 58](https://user-images.githubusercontent.com/27704687/154809653-a8ec9a80-c0dd-4a25-9c00-0f96147d79e9.png)", + "contributors": [ + "juliajforesti", + "dougfabris", + "tassoevan" + ] + }, + { + "pr": "24513", + "title": "Chore: Run tests using microservices deployment on CI", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego", + "web-flow", + "rodrigok" + ] + }, + { + "pr": "24556", + "title": "Bump @types/ws from 8.2.2 to 8.2.3 in /ee/server/services", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24501", + "title": "Chore: Update fuselage deps to match monolith versions", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "24538", + "title": "Bump adm-zip from 0.4.14 to 0.5.9", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24454", + "title": "[IMPROVE] Purchase Type Filter for marketplace apps and Categories filter anchor refactoring", + "userLogin": "rique223", + "description": "Implemented a filter by purchase type(free or paid) component for the apps screen of the marketplace. Besides that, new entries on the dictionary, fixed some parts of the App type (purchaseType was typed as unknown and price as string), and created some helpers to work alongside the filter. Will be refactoring the categories filter anchor and then will open this PR for reviews.\r\n\r\nDemo gif:\r\n![purchaseTypeFIlter](https://user-images.githubusercontent.com/43561537/153101228-7b7ebdc3-2d34-420f-aa9d-f7cbc8d4b53f.gif)\r\n\r\nRefactored the categories filter anchor from a plain fuselage select to a select button with dynamic colors.\r\nDemo gif:\r\n![New categories filter anchor(PR)](https://user-images.githubusercontent.com/43561537/153422427-28012b7d-e0ec-45f4-861d-c9368c57ad04.gif)", + "contributors": [ + "rique223", + "dougfabris", + "web-flow" + ] + }, + { + "pr": "24475", + "title": "[IMPROVE] Skip encryption for slash commands in E2E rooms", + "userLogin": "yash-rajpal", + "description": "Currently Slash Commands don't work in an E2EE room, as we encrypt the message before slash command is detected by the server, So removed encryption for slash commands in e2e rooms.", + "contributors": [ + "yash-rajpal", + "albuquerquefabio", + "web-flow" + ] + }, + { + "pr": "24304", + "title": "Chore: Js to ts slash commands archive", + "userLogin": "eduardofcabrera", + "description": "Convert Slash Commands archive files to typescript", + "contributors": [ + "eduardofcabrera", + "web-flow" + ] + }, + { + "pr": "24114", + "title": "[NEW] E2E password generator", + "userLogin": "ostjen", + "contributors": [ + "ostjen", + "web-flow", + "eduardofcabrera", + "tassoevan" + ] + }, + { + "pr": "24553", + "title": "[FIX] Omnichannel managers can't join chats in progress", + "userLogin": "renatobecker", + "milestone": "4.5.0", + "contributors": [ + "renatobecker", + "murtaza98", + "web-flow" + ] + }, + { + "pr": "24559", + "title": "[FIX] Room context tabs not working in Omnichannel current chats page", + "userLogin": "murtaza98", + "milestone": "4.5.0", + "contributors": [ + "murtaza98" + ] + }, + { + "pr": "24173", + "title": "[FIX] respect `Accounts_Registration_Users_Default_Roles` setting", + "userLogin": "debdutdeb", + "description": "- Fix `user` role being added as default regardless of the `Accounts_Registration_Users_Default_Roles` setting.", + "milestone": "4.5.0", + "contributors": [ + "debdutdeb", + "web-flow", + "matheusbsilva137" + ] + }, + { + "pr": "24485", + "title": "[FIX] Skip admin info in setup wizard for servers with admin registered", + "userLogin": "dougfabris", + "contributors": [ + "dougfabris", + "tassoevan" + ] + }, + { + "pr": "24537", + "title": "Bump pm2 from 5.1.2 to 5.2.0 in /ee/server/services", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24209", + "title": "[IMPROVE] Team system messages feedback", + "userLogin": "ostjen", + "description": "- Delete some keys that aren't being used (eg: User_left_female).\r\n- Add new Teams' system messages:\r\n - `added-user-to-team`: **added** @\\user to this Team;\r\n - `removed-user-from-team`: **removed** @\\user from this Team;\r\n - `user-converted-to-team`: **converted** #\\room to a Team;\r\n - `user-converted-to-channel`: **converted** #\\room to a Channel;\r\n - `user-removed-room-from-team`: **removed** @\\user from this Team;\r\n - `user-deleted-room-from-team`: **deleted** #\\room from this Team;\r\n - `user-added-room-to-team`: **deleted** #\\room to this Team;\r\n- Add the corresponding options to hide each new system message and the missing `ujt` and `ult` hide options.", + "milestone": "4.5.0", + "contributors": [ + "ostjen", + "tassoevan", + "web-flow", + "dougfabris", + "matheusbsilva137" + ] + }, + { + "pr": "24467", + "title": "Chore: Improve PR title validation regex", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego", + "web-flow", + "debdutdeb" + ] + }, + { + "pr": "24058", + "title": "Bump date-fns from 2.24.0 to 2.28.0", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24508", + "title": "[FIX] Read receipts showing first messages of the room as read even if not read by everyone", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego", + "web-flow", + "dougfabris" + ] + }, + { + "pr": "24530", + "title": "Chore: Remove storybook build job from CI", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24528", + "title": "Bump url-parse from 1.5.3 to 1.5.7", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24333", + "title": "Chore: Add description to global OTR setting", + "userLogin": "pedrogssouza", + "contributors": [ + "pedrogssouza", + "yash-rajpal", + "web-flow" + ] + }, + { + "pr": "24382", + "title": "[IMPROVE] OTR system messages", + "userLogin": "yash-rajpal", + "description": "OTR system messages to indicate key refresh and joining chat to users.", + "contributors": [ + "yash-rajpal", + "web-flow" + ] + }, + { + "pr": "24121", + "title": "[IMPROVE] Descriptive tooltip for Encrypted Key on Room Header", + "userLogin": "yash-rajpal", + "milestone": "4.5.0", + "contributors": [ + "yash-rajpal", + "web-flow" + ] + }, + { + "pr": "24522", + "title": "Bump express from 4.17.2 to 4.17.3 in /ee/server/services", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24518", + "title": "Chore: `twoFactorRequired` signature", + "userLogin": "tassoevan", + "description": "Improved type checking for decorator `twoFactorRequired`.", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "24517", + "title": "Bump body-parser from 1.19.1 to 1.19.2 in /ee/server/services", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24441", + "title": "[FIX] GDPR action to forget visitor data on request", + "userLogin": "KevLehman", + "contributors": [ + "KevLehman", + "murtaza98", + "web-flow" + ] + }, + { + "pr": "24306", + "title": "Chore: Convert to typescript the slash commands create files", + "userLogin": "eduardofcabrera", + "description": "Convert Slash Commands create files to typescript.", + "contributors": [ + "eduardofcabrera", + "ostjen", + "web-flow" + ] + }, + { + "pr": "24325", + "title": "Chore: Convert to typescript the mute and unmute slash commands files", + "userLogin": "eduardofcabrera", + "description": "Convert to typescript the mute and unmute slash commands files", + "contributors": [ + "eduardofcabrera", + "ostjen", + "web-flow" + ] + }, + { + "pr": "24321", + "title": "Chore: Convert to typescript the me slashCommands files", + "userLogin": "eduardofcabrera", + "description": "Convert to typescript the me slashCommands files", + "contributors": [ + "eduardofcabrera", + "ostjen", + "web-flow" + ] + }, + { + "pr": "23512", + "title": "Bump sodium-native from 3.2.1 to 3.3.0 in /ee/server/services", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24311", + "title": "Chore: Convert to typescript the slash commands invite files", + "userLogin": "eduardofcabrera", + "description": "Convert to typescript the slash commands invite files", + "contributors": [ + "eduardofcabrera", + "ostjen", + "web-flow" + ] + }, + { + "pr": "24509", + "title": "Bump vm2 from 3.9.5 to 3.9.7 in /ee/server/services", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24451", + "title": "[IMPROVE] ChatBox Text to File Description", + "userLogin": "eduardofcabrera", + "description": "The text content from chatbox goes to the file description when drag and drop a file.", + "contributors": [ + "eduardofcabrera", + "ostjen", + "web-flow", + "dougfabris" + ] + }, + { + "pr": "24461", + "title": "Chore: Update Meteor to 2.5.6", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego", + "rodrigok", + "web-flow" + ] + }, + { + "pr": "24477", + "title": "Chore: Update ws package", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "24498", + "title": "Bump underscore.string from 3.3.5 to 3.3.6 in /ee/server/services", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24491", + "title": "Bump follow-redirects from 1.14.7 to 1.14.8 in /ee/server/services", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24493", + "title": "i18n: Language update from LingoHub 🤖 on 2022-02-14Z", + "userLogin": "lingohub[bot]", + "contributors": [ + null + ] + }, + { + "pr": "24331", + "title": "Chore: Convert to typescript the unarchive slash commands files", + "userLogin": "eduardofcabrera", + "description": "Convert to typescript the unarchive slash commands files", + "contributors": [ + "eduardofcabrera", + "ostjen", + "web-flow" + ] + }, + { + "pr": "24483", + "title": "[IMPROVE] Add tooltips on action buttons of Canned Response message composer", + "userLogin": "LucasFASouza", + "description": "The tooltips were missing on the action buttons of CR message composer.\r\n\r\n![image](https://user-images.githubusercontent.com/32396925/153620327-91107245-4b47-4d39-a99a-6da6d1cf5734.png)\r\n\r\nUsers can now feel more encouraged to use these actions knowing what they are supposed to do.", + "contributors": [ + "LucasFASouza", + "tiagoevanp", + "web-flow" + ] + }, + { + "pr": "24196", + "title": "Chore: Delete unused file (NewAdminInfoPage.js)", + "userLogin": "gabriellsh", + "description": "Just removing a duplicated/unused file.", + "milestone": "4.5.0", + "contributors": [ + "gabriellsh", + "web-flow" + ] + }, + { + "pr": "24388", + "title": "[IMPROVE][ENTERPRISE] Improve how micro services are loaded", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo", + "sampaiodiego" + ] + }, + { + "pr": "24458", + "title": "[IMPROVE] Add return button in chats opened from the list of current chats", + "userLogin": "LucasFASouza", + "description": "The new return button for Omnichannel chats came out with release 3.15 but the feature was only available for chats that were opened from Omnichannel Contact Center.\r\nNow, the same UI/UX is supported for chats opened from Current Chats list.\r\n\r\n![image](https://user-images.githubusercontent.com/32396925/153283190-bd5c9748-c36b-4874-a704-6043afc7e3a1.png)\r\n\r\nThe chat now opens in the Omnichannel settings and has the return button so the user can go back to the Current Chats list.\r\n\r\n![image](https://user-images.githubusercontent.com/32396925/153285591-fad8e4a0-d2ea-4a02-8b2a-15e383b3c876.png)", + "contributors": [ + "LucasFASouza", + "tiagoevanp", + "web-flow" + ] + }, + { + "pr": "24469", + "title": "Bump express from 4.17.1 to 4.17.2 in /ee/server/services", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24472", + "title": "Bump cookie from 0.4.1 to 0.4.2 in /ee/server/services", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24275", + "title": "[IMPROVE] Close modal on esc and outside click", + "userLogin": "gabriellsh", + "description": "This is a QUICK change in order to close modals pressing Esc button and clicking outside of it **intentionally**.", + "milestone": "4.5.0", + "contributors": [ + "gabriellsh", + "dougfabris", + "tassoevan" + ] + }, + { + "pr": "24435", + "title": "Chore(deps-dev): Bump ts-node from 10.0.0 to 10.5.0 in /ee/server/services", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24041", + "title": "[IMPROVE] Add user to room on \"Click to Join!\" button press", + "userLogin": "matheusbsilva137", + "description": "- Add user to room on \"Click to Join!\" button press;\r\n- Display the \"Join\" button in discussions inside channels (keeping the behavior consistent with discussions inside groups).", + "contributors": [ + "matheusbsilva137", + "web-flow", + "tassoevan", + "pierre-lehnen-rc", + "ostjen" + ] + }, + { + "pr": "24310", + "title": "[FIX] Implement client errors on ddp-streamer", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "23963", + "title": "Bump body-parser from 1.19.0 to 1.19.1 in /ee/server/services", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "23961", + "title": "Bump jaeger-client from 3.18.1 to 3.19.0 in /ee/server/services", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24466", + "title": "[FIX] typo on register server tooltip of setup wizard", + "userLogin": "filipemarins", + "milestone": "4.5.0", + "contributors": [ + "filipemarins" + ] + }, + { + "pr": "24037", + "title": "[FIX] Inconsistent validation of user's access to rooms", + "userLogin": "pierre-lehnen-rc", + "contributors": [ + "pierre-lehnen-rc", + "ostjen", + "web-flow" + ] + }, + { + "pr": "24450", + "title": "[FIX] OAuth mismatch redirect_uri error", + "userLogin": "sampaiodiego", + "milestone": "4.4.2", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24305", + "title": "[FIX] Prevent Apps Bridge to remove visitor status from room", + "userLogin": "KevLehman", + "contributors": [ + "KevLehman", + "d-gubert" + ] + }, + { + "pr": "24453", + "title": "Chore: bump fuselage version", + "userLogin": "dougfabris", + "milestone": "4.4.2", + "contributors": [ + "dougfabris" + ] + }, + { + "pr": "24253", + "title": "[FIX] Issues on selecting users when importing CSV", + "userLogin": "guijun13", + "description": "* Fix users selecting by fixing their _id\r\n* Add condition to disable 'Start importing' button if `usersCount`, `channelsCount` and `messageCount` equals 0, or if messageCount is alone\r\n* Remove `disabled={usersCount === 0}` on user Tab", + "contributors": [ + "guijun13", + "tassoevan", + "web-flow", + "pierre-lehnen-rc" + ] + }, + { + "pr": "24299", + "title": "Chore(deps): Bump node-fetch from 2.6.1 to 2.6.7 in /ee/server/services", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24418", + "title": "[FIX] Oembed request not respecting payload limit", + "userLogin": "sampaiodiego", + "milestone": "4.4.1", + "contributors": [ + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "24429", + "title": "i18n: Language update from LingoHub 🤖 on 2022-02-07Z", + "userLogin": "lingohub[bot]", + "contributors": [ + null, + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "24407", + "title": "[FIX] Skip cloud steps for registered servers on setup wizard", + "userLogin": "dougfabris", + "milestone": "4.4.1", + "contributors": [ + "dougfabris", + "tassoevan", + "gabriellsh", + "web-flow" + ] + }, + { + "pr": "24410", + "title": "Chore: Convert JS files to Typescript", + "userLogin": "felipe-rod123", + "description": "This pull request converts 26 more files from Javascript to Typescript, to check variable types and increase validation on the code.", + "contributors": [ + "felipe-rod123", + "ggazzo", + "web-flow" + ] + }, + { + "pr": "24369", + "title": "[IMPROVE] Convert tag edit with department data to tsx", + "userLogin": "LucasFASouza", + "contributors": [ + "LucasFASouza", + "tiagoevanp", + "web-flow" + ] + }, + { + "pr": "24401", + "title": "[FIX] Outgoing webhook without scripts not saving messages", + "userLogin": "sampaiodiego", + "milestone": "4.4.1", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24334", + "title": "[IMPROVE] CloudLoginModal visual consistency", + "userLogin": "dougfabris", + "description": "### before\r\n![image](https://user-images.githubusercontent.com/27704687/151585064-dc6a1e29-9903-4241-8fbd-dfbe6c55fbef.png)\r\n\r\n### after\r\n![Screen Shot 2022-01-28 at 13 32 02](https://user-images.githubusercontent.com/27704687/151585101-75b98502-9aae-4198-bc3e-4956750e5d8b.png)", + "milestone": "4.5.0", + "contributors": [ + "dougfabris", + "gabriellsh", + "web-flow" + ] + }, + { + "pr": "24409", + "title": "[FIX] Startup errors creating indexes", + "userLogin": "sampaiodiego", + "description": "Fix `bio` and `prid` startup index creation errors.", + "milestone": "4.4.1", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24406", + "title": "Chore: Unify ILivechatAgent with ILivechatAgentRecord", + "userLogin": "KevLehman", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "24381", + "title": "[FIX] Add ?close to OAuth callback url", + "userLogin": "sampaiodiego", + "milestone": "4.4.1", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24387", + "title": "[FIX] Slash commands previews not working", + "userLogin": "ostjen", + "milestone": "4.4.1", + "contributors": [ + "ostjen" + ] + }, + { + "pr": "24357", + "title": "i18n: Language update from LingoHub 🤖 on 2022-01-31Z", + "userLogin": "lingohub[bot]", + "contributors": [ + null + ] + }, + { + "pr": "24341", + "title": "Bump simple-get from 4.0.0 to 4.0.1", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24366", + "title": "Chore: Set Docker image tag to latest only when really latest", + "userLogin": "debdutdeb", + "contributors": [ + "debdutdeb", + "web-flow" + ] + }, + { + "pr": "24109", + "title": "[IMPROVE] Added a new \"All\" tab which shows all integrations in Integrations", + "userLogin": "aswinidev", + "milestone": "4.5.0", + "contributors": [ + "aswinidev", + "dougfabris", + "web-flow" + ] + }, + { + "pr": "24363", + "title": "Merge master into develop & Set version to 4.5.0-develop", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego", + "web-flow" + ] + } + ] + }, "4.4.1": { "node_version": "14.18.2", "npm_version": "6.14.15", - "apps_engine_version": "1.30.0", + "apps_engine_version": "1.30.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "24432", + "title": "Release 4.4.1", + "userLogin": "pierre-lehnen-rc", + "contributors": [ + "sampaiodiego", + "pierre-lehnen-rc", + "dougfabris", + "ostjen" + ] + }, + { + "pr": "24387", + "title": "[FIX] Slash commands previews not working", + "userLogin": "ostjen", + "milestone": "4.4.1", + "contributors": [ + "ostjen" + ] + }, + { + "pr": "24381", + "title": "[FIX] Add ?close to OAuth callback url", + "userLogin": "sampaiodiego", + "milestone": "4.4.1", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24409", + "title": "[FIX] Startup errors creating indexes", + "userLogin": "sampaiodiego", + "description": "Fix `bio` and `prid` startup index creation errors.", + "milestone": "4.4.1", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24401", + "title": "[FIX] Outgoing webhook without scripts not saving messages", + "userLogin": "sampaiodiego", + "milestone": "4.4.1", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24407", + "title": "[FIX] Skip cloud steps for registered servers on setup wizard", + "userLogin": "dougfabris", + "milestone": "4.4.1", + "contributors": [ + "dougfabris", + "tassoevan", + "gabriellsh", + "web-flow" + ] + }, + { + "pr": "24418", + "title": "[FIX] Oembed request not respecting payload limit", + "userLogin": "sampaiodiego", + "milestone": "4.4.1", + "contributors": [ + "sampaiodiego", + "web-flow" + ] + } + ] + }, + "4.4.2": { + "node_version": "14.18.2", + "npm_version": "6.14.15", + "apps_engine_version": "1.30.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "24459", + "title": "Release 4.4.2", + "userLogin": "pierre-lehnen-rc", + "contributors": [ + "dougfabris", + "pierre-lehnen-rc", + "sampaiodiego" + ] + }, + { + "pr": "24450", + "title": "[FIX] OAuth mismatch redirect_uri error", + "userLogin": "sampaiodiego", + "milestone": "4.4.2", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24453", + "title": "Chore: bump fuselage version", + "userLogin": "dougfabris", + "milestone": "4.4.2", + "contributors": [ + "dougfabris" + ] + } + ] + }, + "4.5.0-rc.1": { + "node_version": "14.18.3", + "npm_version": "6.14.15", + "apps_engine_version": "1.31.0-alpha.5979", + "mongo_versions": [ + "'3.6'", + "'4.0'", + "'4.2'", + "'4.4'", + "'5.0'" + ], + "pull_requests": [ + { + "pr": "24581", + "title": "Regression: Add support to namespace within micro services", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24583", + "title": "Regression: Error when trying to load name of dm rooms for avatars and notifications", + "userLogin": "pierre-lehnen-rc", + "contributors": [ + "pierre-lehnen-rc" + ] + }, + { + "pr": "24567", + "title": "[NEW] Marketplace sort filter", + "userLogin": "ujorgeleite", + "description": "Implemented a sort filter for the marketplace screen. This component sorts the marketplace apps list in 4 ways, alphabetical order(A-Z), inverse alphabetical order(Z-A), most recently updated(MRU), and least recent updated(LRU). Besides that, I've generalized some components and types to increase code reusability, renamed some helpers as well as deleted some useless ones, and inserted the necessary new translations on the English i18n dictionary.\r\nDemo gif:\r\n![Marketplace sort filter](https://user-images.githubusercontent.com/43561537/155033709-e07a6306-a85a-4f7f-9624-b53ba5dd7fa9.gif)", + "milestone": "4.5.0", + "contributors": [ + "rique223", + "ujorgeleite" + ] + }, + { + "pr": "23102", + "title": "[NEW] VoIP Support for Omnichannel", + "userLogin": "KevLehman", + "description": "- Created VoipService to manage VoIP connections and PBX connection\r\n- Created LivechatVoipService that will handle custom cases for livechat (creating rooms, assigning chats to queue, actions when call is finished, etc)\r\n- Created Basic interfaces to support new services and new model\r\n- Created Endpoints for management interfaces\r\n- Implemented asterisk connector on VoIP service\r\n- Created UI components to show calls incoming and to allow answering/rejecting calls\r\n- Added new settings to control call server/management server connection values\r\n- Added endpoints to associate Omnichannel Agents with PBX Extensions\r\n- Added support for event listening on server side, to get metadata about calls being received/ongoing\r\n- Created new pages to update settings & to see user-extension association\r\n- Created new page to see ongoing calls (and past calls)\r\n- Added support for remote hangup/hold on calls\r\n- Implemented call metrics calculation (hold time, waiting time, talk time)\r\n- Show a notificaiton when call is received", + "milestone": "4.5.0", + "contributors": [ + "KevLehman", + "amolghode1981", + "web-flow", + "tiagoevanp", + "murtaza98", + "MartinSchoeler" + ] + }, + { + "pr": "24562", + "title": "Regression: Fix room not getting created due to null visitor status", + "userLogin": "murtaza98", + "milestone": "4.5.0", + "contributors": [ + "murtaza98" + ] + } + ] + }, + "4.5.0-rc.2": { + "node_version": "14.18.3", + "npm_version": "6.14.15", + "apps_engine_version": "1.31.0-alpha.5979", + "mongo_versions": [ + "'3.6'", + "'4.0'", + "'4.2'", + "'4.4'", + "'5.0'" + ], + "pull_requests": [ + { + "pr": "24594", + "title": "Regression: Bunch of settings fixes for VoIP", + "userLogin": "MartinSchoeler", + "milestone": "4.5.0", + "contributors": [ + "MartinSchoeler", + "web-flow" + ] + }, + { + "pr": "24609", + "title": "Regression: Admin Sidebar colors inverted.", + "userLogin": "gabriellsh", + "milestone": "4.5.0", + "contributors": [ + "gabriellsh" + ] + }, + { + "pr": "24602", + "title": "Regression: No audio when call comes from Skype/IP phone", + "userLogin": "amolghode1981", + "description": "The audio was not rendered because of re-rendering of react element based on\r\nqueueCounter and roomInfo. queueCounter and roomInfo cause the dom to re-render when call gets accepted\r\nbecause after accepting call, queueCounter changes or a room gets created.\r\nThe audio element gets recreated. But VoIP user probably holds the old one.\r\nThe behaviour is not predictable when such case happens. If everything gets cleanly setup,\r\neven if the audio element goes headless, it still continues to play the remote audio.\r\nBut in other cases, it is unreferenced the one on dom has its srcObject as null.\r\nThis causes no audio.\r\n\r\nThis fix provides a way to re-initialise the rendering elements in VoIP user\r\nand calls this function on useEffect() if the re-render has happen.", + "milestone": "4.5.0", + "contributors": [ + "amolghode1981" + ] + }, + { + "pr": "24596", + "title": "Regression: Fixes in Voice Contextual Bar and Directory", + "userLogin": "MartinSchoeler", + "milestone": "4.5.0", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "24603", + "title": "Regression: Fix time format on Voip system messages", + "userLogin": "murtaza98", + "milestone": "4.5.0", + "contributors": [ + "murtaza98" + ] + }, + { + "pr": "24598", + "title": "Regression: VoIP service button displayed when VoIP is disabled", + "userLogin": "murtaza98", + "milestone": "4.5.0", + "contributors": [ + "murtaza98" + ] + } + ] + }, + "4.5.0-rc.3": { + "node_version": "14.18.3", + "npm_version": "6.14.15", + "apps_engine_version": "1.31.0-alpha.5979", + "mongo_versions": [ + "'3.6'", + "'4.0'", + "'4.2'", + "'4.4'", + "'5.0'" + ], + "pull_requests": [ + { + "pr": "24630", + "title": "Regression: Fix double value on holdTime and empty msg on last message", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler", + "web-flow" + ] + }, + { + "pr": "24624", + "title": "Regression: If Asterisk suddenly goes down, server has no way to know. Causes server to get stuck. Needs restart", + "userLogin": "amolghode1981", + "milestone": "4.5.0", + "contributors": [ + "amolghode1981", + "KevLehman" + ] + }, + { + "pr": "24601", + "title": "Regression: Prevent connect to asterisk when VoIP is disabled", + "userLogin": "murtaza98", + "milestone": "4.5.0", + "contributors": [ + "murtaza98", + "web-flow", + "KevLehman" + ] + }, + { + "pr": "24626", + "title": "Regression: Encode registration info as JWT when signing key is provided", + "userLogin": "KevLehman", + "milestone": "4.5.0", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "24625", + "title": "Regression: Fix time fields and wrap up in Voip Room Contexual bar", + "userLogin": "MartinSchoeler", + "milestone": "4.5.0", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "24592", + "title": "Regression: Fix in-correct room status shown to agents", + "userLogin": "murtaza98", + "milestone": "4.5.0", + "contributors": [ + "murtaza98" + ] + }, + { + "pr": "24619", + "title": "Regression: Do not show toast on incoming voip calls", + "userLogin": "murtaza98", + "milestone": "4.5.0", + "contributors": [ + "murtaza98", + "web-flow" + ] + }, + { + "pr": "24616", + "title": "Regression: Fix incoming voip call ringtone is not ringing", + "userLogin": "murtaza98", + "contributors": [ + "murtaza98", + "web-flow" + ] + }, + { + "pr": "24610", + "title": "Regression: Mark all rooms as read modal closing instantly.", + "userLogin": "gabriellsh", + "contributors": [ + "gabriellsh" + ] + }, + { + "pr": "24615", + "title": "Regression: Fix translation for call started message", + "userLogin": "murtaza98", + "milestone": "4.5.0", + "contributors": [ + "murtaza98", + "web-flow" + ] + } + ] + }, + "4.5.0-rc.4": { + "node_version": "14.18.3", + "npm_version": "6.14.15", + "apps_engine_version": "1.31.0-alpha.5979", + "mongo_versions": [ + "'3.6'", + "'4.0'", + "'4.2'", + "'4.4'", + "'5.0'" + ], + "pull_requests": [ + { + "pr": "24585", + "title": "Regression: Error setting user avatars and mentioning rooms on Slack Import", + "userLogin": "matheusbsilva137", + "description": "- Fix `Mentioned room not found` error when importing rooms from Slack;\r\n- Fix `Forbidden` error when setting avatars for users imported from Slack (on user import/creation);\r\n- Fix incorrect message count on imported rooms;\r\n- Fix missing username on messages imported from Slack;", + "contributors": [ + "matheusbsilva137", + "pierre-lehnen-rc" + ] + }, + { + "pr": "24647", + "title": "Regression: Fix wrong tab name for VoIP settings", + "userLogin": "renatobecker", + "milestone": "4.5.0", + "contributors": [ + "renatobecker" + ] + }, + { + "pr": "24646", + "title": "Regression: Server crashing if Voip credentials are invalid", + "userLogin": "murtaza98", + "milestone": "4.5.0", + "contributors": [ + "murtaza98" + ] + }, + { + "pr": "24645", + "title": "Regression: Extension List panel UI not aligned with designs", + "userLogin": "murtaza98", + "milestone": "4.5.0", + "contributors": [ + "murtaza98" + ] + }, + { + "pr": "24635", + "title": "Regression: Queue counter aggregator for incoming/hanged calls", + "userLogin": "amolghode1981", + "milestone": "4.5.0", + "contributors": [ + "amolghode1981" + ] + } + ] + }, + "4.5.0-rc.5": { + "node_version": "14.18.3", + "npm_version": "6.14.15", + "apps_engine_version": "1.31.0-alpha.5979", + "mongo_versions": [ + "'3.6'", + "'4.0'", + "'4.2'", + "'4.4'", + "'5.0'" + ], + "pull_requests": [ + { + "pr": "24649", + "title": "Regression: Refresh server connection when MI server settings change", + "userLogin": "KevLehman", + "milestone": "4.5.0", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "24648", + "title": "Regression: Prevent button from losing state when rerendering", + "userLogin": "KevLehman", + "milestone": "4.5.0", + "contributors": [ + "KevLehman" + ] + } + ] + }, + "4.5.0-rc.6": { + "node_version": "14.18.3", + "npm_version": "6.14.15", + "apps_engine_version": "1.31.0", + "mongo_versions": [ + "'3.6'", + "'4.0'", + "'4.2'", + "'4.4'", + "'5.0'" + ], + "pull_requests": [ + { + "pr": "24651", + "title": "Chore: Update Apps-Engine", + "userLogin": "d-gubert", + "milestone": "4.5.0", + "contributors": [ + "d-gubert" + ] + } + ] + }, + "4.5.0-rc.7": { + "node_version": "14.18.3", + "npm_version": "6.14.15", + "apps_engine_version": "1.31.0", + "mongo_versions": [ + "'3.6'", + "'4.0'", + "'4.2'", + "'4.4'", + "'5.0'" + ], + "pull_requests": [] + }, + "4.5.0": { + "node_version": "14.18.3", + "npm_version": "6.14.15", + "apps_engine_version": "1.31.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [] + }, + "4.5.1": { + "node_version": "14.18.3", + "npm_version": "6.14.15", + "apps_engine_version": "1.31.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "24782", + "title": "Release 4.5.1", + "userLogin": "pierre-lehnen-rc", + "contributors": [ + "renatobecker", + "pierre-lehnen-rc", + "sampaiodiego", + "matheusbsilva137", + "amolghode1981", + "juliajforesti", + "tiagoevanp", + "KevLehman", + "MartinSchoeler", + "Aman-Maheshwari", + "cuonghuunguyen" + ] + }, + { + "pr": "24760", + "title": "[FIX] Apple login script being loaded even when Apple Login is disabled.", + "userLogin": "pierre-lehnen-rc", + "milestone": "4.5.1", + "contributors": [ + "pierre-lehnen-rc" + ] + }, + { + "pr": "24754", + "title": "Chore: Update Livechat", + "userLogin": "MartinSchoeler", + "milestone": "4.5.1", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "24683", + "title": "[FIX] no id of room closer in livechat-close message", + "userLogin": "cuonghuunguyen", + "milestone": "4.5.1", + "contributors": [ + null + ] + }, + { + "pr": "23795", + "title": "[FIX] Reload roomslist after successful deletion of a room from admin panel.", + "userLogin": "Aman-Maheshwari", + "description": "Removed the logic for calling the `rooms.adminRooms` endPoint from the `RoomsTable` Component and moved it to its parent component `RoomsPage`.\r\nThis allows to call the endPoint `rooms.adminRooms` from `EditRoomContextBar` Component which is also has `RoomPage` Component as its parent.\r\n\r\nAlso added a succes toast message after the successful deletion of room.", + "milestone": "4.5.1", + "contributors": [ + "Aman-Maheshwari", + "web-flow", + "tassoevan" + ] + }, + { + "pr": "24743", + "title": "[FIX] System messages are sent when adding or removing a group from a team", + "userLogin": "matheusbsilva137", + "description": "- Do not send system messages when adding or removing a new or existing _group_ from a team.", + "milestone": "4.5.1", + "contributors": [ + "matheusbsilva137" + ] + }, + { + "pr": "24737", + "title": "[FIX] Typo and placeholder on wrap up call modal", + "userLogin": "MartinSchoeler", + "milestone": "4.5.1", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "24680", + "title": "[FIX] Show only available agents on extension association modal", + "userLogin": "KevLehman", + "milestone": "4.5.1", + "contributors": [ + "KevLehman", + "tiagoevanp" + ] + }, + { + "pr": "24607", + "title": "[FIX] VoIP Enable/Disable setting on CallContext/CallProvider Notifications", + "userLogin": "tiagoevanp", + "milestone": "4.5.1", + "contributors": [ + "tiagoevanp", + "web-flow", + "tassoevan" + ] + }, + { + "pr": "24677", + "title": "[FIX] Components for user search", + "userLogin": "juliajforesti", + "milestone": "4.5.1", + "contributors": [ + "juliajforesti", + "tassoevan", + "web-flow" + ] + }, + { + "pr": "24657", + "title": "[FIX] Voip Stream Reinitialization Error", + "userLogin": "amolghode1981", + "milestone": "4.5.1", + "contributors": [ + "amolghode1981" + ] + }, + { + "pr": "24696", + "title": "[FIX] Room's message count not being incremented on import", + "userLogin": "matheusbsilva137", + "description": "- Fix rooms' message counter not being incremented on message import.", + "milestone": "4.5.1", + "contributors": [ + "matheusbsilva137" + ] + }, + { + "pr": "24674", + "title": "[FIX] Missing username on messages imported from Slack", + "userLogin": "matheusbsilva137", + "description": "- Fix missing sender's username on messages imported from Slack.", + "milestone": "4.5.1", + "contributors": [ + "matheusbsilva137" + ] + }, + { + "pr": "24590", + "title": "[FIX] Duplicated 'name' log key", + "userLogin": "sampaiodiego", + "milestone": "4.5.1", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24661", + "title": "[FIX] Typo in wrap-up term", + "userLogin": "renatobecker", + "milestone": "4.5.1", + "contributors": [ + "renatobecker" + ] + } + ] + }, + "4.5.2": { + "node_version": "14.18.3", + "npm_version": "6.14.15", + "apps_engine_version": "1.31.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "24812", + "title": "[FIX] Revert AutoComplete", + "userLogin": "juliajforesti", + "milestone": "4.5.2", + "contributors": [ + "juliajforesti", + "ggazzo" + ] + }, + { + "pr": "24809", + "title": "Regression: Fix ParentRoomWithEndpointData in loop", + "userLogin": "sampaiodiego", + "milestone": "4.5.2", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24732", + "title": "[FIX] `PaginatedSelectFiltered` not handling changes", + "userLogin": "tassoevan", + "milestone": "4.5.2", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "24805", + "title": "[FIX] Critical: Incorrect visitor getting assigned to a chat from apps", + "userLogin": "murtaza98", + "milestone": "4.5.2", + "contributors": [ + "murtaza98" + ] + }, + { + "pr": "24804", + "title": "[FIX] \"livechat/webrtc.call\" endpoint not working", + "userLogin": "murtaza98", + "milestone": "4.5.2", + "contributors": [ + "murtaza98" + ] + }, + { + "pr": "24792", + "title": "[FIX] VoipExtensionsPage component call", + "userLogin": "KevLehman", + "milestone": "4.5.2", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "24705", + "title": "[FIX] Broken multiple OAuth integrations", + "userLogin": "debdutdeb", + "milestone": "4.5.2", + "contributors": [ + "debdutdeb" + ] + }, + { + "pr": "24623", + "title": "[FIX] Opening a new DM from user card", + "userLogin": "tassoevan", + "description": "A race condition on `useRoomIcon` -- delayed merge of rooms and subscriptions -- was causing a UI crash whenever someone tried to open a DM from the user card component.", + "milestone": "4.5.2", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "24750", + "title": "[IMPROVE] Voip Extensions disabled state", + "userLogin": "MartinSchoeler", + "milestone": "4.5.2", + "contributors": [ + "MartinSchoeler" + ] + } + ] + }, + "4.5.3": { + "node_version": "14.18.3", + "npm_version": "6.14.15", + "apps_engine_version": "1.31.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "24901", + "title": "[FIX] Custom script not being fired", + "userLogin": "ggazzo", + "milestone": "4.5.3", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "24877", + "title": "Chore: Fix MongoDB versions on release notes", + "userLogin": "sampaiodiego", + "milestone": "4.5.3", + "contributors": [ + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "24864", + "title": "[FIX] Disable voip button when call is in progress", + "userLogin": "KevLehman", + "milestone": "4.5.3", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "24863", + "title": "[FIX] Broken build caused by PRs modifying same file differently", + "userLogin": "KevLehman", + "milestone": "4.5.3", + "contributors": [ + "KevLehman", + "tiagoevanp" + ] + }, + { + "pr": "24838", + "title": "[FIX] [VOIP] SidebarFooter component ", + "userLogin": "tiagoevanp", + "description": "- Improve the CallProvider code;\r\n- Adjust the text case of the VoIP component on the FooterSidebar;\r\n- Fix the bad behavior with the changes in queue's name.", + "milestone": "4.5.3", + "contributors": [ + "tiagoevanp" + ] + }, + { + "pr": "24837", + "title": "[IMPROVE] Standarize queue behavior for managers and agents when subscribing", + "userLogin": "KevLehman", + "milestone": "4.5.3", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "24829", + "title": "[FIX] Show only enabled departments on forward", + "userLogin": "KevLehman", + "milestone": "4.5.3", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "24799", + "title": "[FIX] Wrong param usage on queue summary call", + "userLogin": "KevLehman", + "milestone": "4.5.3", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "24789", + "title": "[FIX] VoIP button gets disabled whenever user status changes", + "userLogin": "amolghode1981", + "milestone": "4.5.3", + "contributors": [ + "amolghode1981" + ] + }, + { + "pr": "24752", + "title": "[FIX] Show call icon only when user has extension associated", + "userLogin": "KevLehman", + "milestone": "4.5.3", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "24748", + "title": "[IMPROVE] UX - VoIP Call Component", + "userLogin": "tiagoevanp", + "milestone": "4.5.3", + "contributors": [ + "tiagoevanp" + ] + } + ] + }, + "4.6.0-rc.0": { + "node_version": "14.18.3", + "npm_version": "6.14.15", + "apps_engine_version": "1.31.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "24052", + "title": "[FIX] Several issues related to custom roles", + "userLogin": "pierre-lehnen-rc", + "description": "- Throw an error when trying to delete a role (User or Subscription role) that are still being used;\r\n- Fix \"Invalid Role\" error for custom roles in Role Editing sidebar;\r\n- Fix \"Users in Role\" screen for custom roles.", + "milestone": "4.6.0", + "contributors": [ + "pierre-lehnen-rc", + "matheusbsilva137", + "web-flow" + ] + }, + { + "pr": "24781", + "title": "[NEW] Telemetry Events", + "userLogin": "eduardofcabrera", + "contributors": [ + "eduardofcabrera", + "ostjen", + "web-flow" + ] + }, + { + "pr": "24887", + "title": "[IMPROVE] Adding new statistics related to voip and omnichannel", + "userLogin": "cauefcr", + "description": "- Total of Canned response messages sent\r\n- Total of tags used\r\n- Last-Chatted Agent Preferred (enabled/disabled)\r\n- Assign new conversations to the contact manager (enabled/disabled)\r\n- How to handle Visitor Abandonment setting\r\n- Amount of chats placed on hold\r\n- VoIP Enabled\r\n- Amount of VoIP Calls\r\n- Amount of VoIP Extensions connected\r\n- Amount of Calls placed on hold (1x per call)\r\n- Fixed Session Aggregation type definitions", + "milestone": "4.6.0", + "contributors": [ + "cauefcr", + "KevLehman" + ] + }, + { + "pr": "24911", + "title": "Chore: Remove old scripts", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24898", + "title": "[FIX] DDP Rate Limiter Translation key", + "userLogin": "gabriellsh", + "description": "Before:\r\n\"image\"\r\n\r\n\r\nNow:\r\n\"image\"", + "contributors": [ + "gabriellsh" + ] + }, + { + "pr": "24831", + "title": "[FIX][ENTERPRISE] Notifications not being sent by ddp-streamer", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24606", + "title": "[FIX] Push privacy config to not show username not being respected", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24901", + "title": "[FIX] Custom script not being fired", + "userLogin": "ggazzo", + "milestone": "4.5.3", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "24896", + "title": "[FIX] Wrong business hour behavior", + "userLogin": "murtaza98", + "milestone": "4.6.0", + "contributors": [ + "murtaza98" + ] + }, + { + "pr": "24845", + "title": "[FIX] Ignore customClass on messages", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24879", + "title": "[FIX] Apple OAuth", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "24895", + "title": "i18n: Language update from LingoHub 🤖 on 2022-03-21Z", + "userLogin": "lingohub[bot]", + "contributors": [ + null + ] + }, + { + "pr": "24749", + "title": "[IMPROVE] New omnichannel statistics and async statistics processing.", + "userLogin": "cauefcr", + "description": "https://app.clickup.com/t/1z4zg4e", + "contributors": [ + "cauefcr" + ] + }, + { + "pr": "24882", + "title": "[FIX] Missing dependency on useEffect at CallProvider", + "userLogin": "KevLehman", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "24877", + "title": "Chore: Fix MongoDB versions on release notes", + "userLogin": "sampaiodiego", + "milestone": "4.5.3", + "contributors": [ + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "24779", + "title": "[FIX] auto-join team channels not honoring user preferences", + "userLogin": "ostjen", + "contributors": [ + "ostjen" + ] + }, + { + "pr": "24869", + "title": "Bump pino from 7.8.1 to 7.9.1 in /ee/server/services", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24870", + "title": "Bump pino-pretty from 7.5.3 to 7.5.4 in /ee/server/services", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24864", + "title": "[FIX] Disable voip button when call is in progress", + "userLogin": "KevLehman", + "milestone": "4.5.3", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "24863", + "title": "[FIX] Broken build caused by PRs modifying same file differently", + "userLogin": "KevLehman", + "milestone": "4.5.3", + "contributors": [ + "KevLehman", + "tiagoevanp" + ] + }, + { + "pr": "24850", + "title": "Regression: Role Sync not always working", + "userLogin": "pierre-lehnen-rc", + "contributors": [ + "pierre-lehnen-rc" + ] + }, + { + "pr": "24838", + "title": "[FIX] [VOIP] SidebarFooter component ", + "userLogin": "tiagoevanp", + "description": "- Improve the CallProvider code;\r\n- Adjust the text case of the VoIP component on the FooterSidebar;\r\n- Fix the bad behavior with the changes in queue's name.", + "milestone": "4.5.3", + "contributors": [ + "tiagoevanp" + ] + }, + { + "pr": "24837", + "title": "[IMPROVE] Standarize queue behavior for managers and agents when subscribing", + "userLogin": "KevLehman", + "milestone": "4.5.3", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "24789", + "title": "[FIX] VoIP button gets disabled whenever user status changes", + "userLogin": "amolghode1981", + "milestone": "4.5.3", + "contributors": [ + "amolghode1981" + ] + }, + { + "pr": "24799", + "title": "[FIX] Wrong param usage on queue summary call", + "userLogin": "KevLehman", + "milestone": "4.5.3", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "24829", + "title": "[FIX] Show only enabled departments on forward", + "userLogin": "KevLehman", + "milestone": "4.5.3", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "24823", + "title": "i18n: Language update from LingoHub 🤖 on 2022-03-14Z", + "userLogin": "lingohub[bot]", + "contributors": [ + null, + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "24833", + "title": "Bump @types/mailparser from 3.0.2 to 3.4.0", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24832", + "title": "Bump @types/clipboard from 2.0.1 to 2.0.7", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24822", + "title": "Bump @types/nodemailer from 6.4.2 to 6.4.4", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24821", + "title": "Bump body-parser from 1.19.0 to 1.19.2", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24820", + "title": "Bump @types/ws from 8.5.2 to 8.5.3 in /ee/server/services", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24764", + "title": "Chore: Add E2E tests for livechat/visitor", + "userLogin": "Muramatsu2602", + "description": "- Create a new test suite file under tests/end-to-end/api/livechat\r\n- Create tests for the following endpoints:\r\n + livechat/visitor (create visitor, update visitor, add custom fields to visitors)", + "contributors": [ + "Muramatsu2602", + "KevLehman" + ] + }, + { + "pr": "24729", + "title": "Chore: Add E2E tests for livechat/room.close", + "userLogin": "Muramatsu2602", + "description": "* Create a new test suite file under tests/end-to-end/api/livechat\r\n * Create tests for the following endpoint:\r\n\t + ivechat/room.close", + "contributors": [ + "Muramatsu2602", + "web-flow", + "KevLehman" + ] + }, + { + "pr": "24785", + "title": "[FIX] German translation for Monitore", + "userLogin": "JMoVS", + "contributors": [ + "JMoVS", + "web-flow" + ] + }, + { + "pr": "24812", + "title": "[FIX] Revert AutoComplete", + "userLogin": "juliajforesti", + "milestone": "4.5.2", + "contributors": [ + "juliajforesti", + "ggazzo" + ] + }, + { + "pr": "24747", + "title": "Chore: APIClass types", + "userLogin": "felipe-rod123", + "description": "This pull request creates a new `restivus` module (.d.ts) for the `api.js` file.", + "contributors": [ + "felipe-rod123", + "ggazzo" + ] + }, + { + "pr": "24801", + "title": "Bump is-svg from 4.3.1 to 4.3.2", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24803", + "title": "Bump prometheus-gc-stats from 0.6.2 to 0.6.3", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24810", + "title": "Chore: Skip local services changes when shutting down duplicated services", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24629", + "title": "[FIX] \"Match error\" when converting a team to a channel", + "userLogin": "matheusbsilva137", + "description": "- Fix \"Match error\" when trying to convert a channel to a team;", + "milestone": "4.6.0", + "contributors": [ + "matheusbsilva137", + "web-flow" + ] + }, + { + "pr": "24809", + "title": "Regression: Fix ParentRoomWithEndpointData in loop", + "userLogin": "sampaiodiego", + "milestone": "4.5.2", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24397", + "title": "Chore: Get Settings Statistics", + "userLogin": "albuquerquefabio", + "contributors": [ + "albuquerquefabio" + ] + }, + { + "pr": "24732", + "title": "[FIX] `PaginatedSelectFiltered` not handling changes", + "userLogin": "tassoevan", + "milestone": "4.5.2", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "24628", + "title": "Chore: converted more hooks to typescript", + "userLogin": "felipe-rod123", + "description": "Converted some functions on `client/hooks/` from JavaScript to Typescript.", + "contributors": [ + "felipe-rod123", + "ggazzo" + ] + }, + { + "pr": "24506", + "title": "Chore: added settings endpoint types", + "userLogin": "felipe-rod123", + "description": "Created typing for endpoint definitions on `settings.ts`.", + "contributors": [ + "felipe-rod123", + "ggazzo", + "web-flow" + ] + }, + { + "pr": "24226", + "title": "[FIX] Handle Other Formats inside Upload Avatar", + "userLogin": "nishant23122000", + "description": "After resolving issue #24213 : \r\n\r\n\r\nhttps://user-images.githubusercontent.com/53515714/150325012-91413025-786e-4ce0-ae75-629f6b05b024.mp4", + "milestone": "4.6.0", + "contributors": [ + "nishant23122000", + "debdutdeb", + "web-flow", + "murtaza98" + ] + }, + { + "pr": "24424", + "title": "[FIX] Prune Message issue", + "userLogin": "nishant23122000", + "milestone": "4.6.0", + "contributors": [ + "nishant23122000", + "debdutdeb", + "web-flow" + ] + }, + { + "pr": "24805", + "title": "[FIX] Critical: Incorrect visitor getting assigned to a chat from apps", + "userLogin": "murtaza98", + "milestone": "4.5.2", + "contributors": [ + "murtaza98" + ] + }, + { + "pr": "24804", + "title": "[FIX] \"livechat/webrtc.call\" endpoint not working", + "userLogin": "murtaza98", + "milestone": "4.5.2", + "contributors": [ + "murtaza98" + ] + }, + { + "pr": "24507", + "title": "Chore: added Server Instances endpoint types", + "userLogin": "felipe-rod123", + "description": "Created typing for endpoint definitions on `instances.ts`.", + "contributors": [ + "felipe-rod123" + ] + }, + { + "pr": "24758", + "title": "[FIX] Prevent call button toggle when user is on call", + "userLogin": "KevLehman", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "24800", + "title": "Regression: Register services right away", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24792", + "title": "[FIX] VoipExtensionsPage component call", + "userLogin": "KevLehman", + "milestone": "4.5.2", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "24384", + "title": "Chore: Convert server functions from javascript to typescript", + "userLogin": "felipe-rod123", + "description": "This pull request will be used to rewrite some functions on the Chat Engine to Typescript, in order to increase security and specify variable types on the code.", + "contributors": [ + "felipe-rod123", + "ggazzo" + ] + }, + { + "pr": "24705", + "title": "[FIX] Broken multiple OAuth integrations", + "userLogin": "debdutdeb", + "milestone": "4.5.2", + "contributors": [ + "debdutdeb" + ] + }, + { + "pr": "24793", + "title": "[FIX][ENTERPRISE] Auto reload feature of ddp-streamer micro service", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24783", + "title": "Bump pino from 7.8.0 to 7.8.1 in /ee/server/services", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "23121", + "title": "Bump jschardet from 1.6.0 to 3.0.0", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24752", + "title": "[FIX] Show call icon only when user has extension associated", + "userLogin": "KevLehman", + "milestone": "4.5.3", + "contributors": [ + "KevLehman" + ] + }, + { + "pr": "24623", + "title": "[FIX] Opening a new DM from user card", + "userLogin": "tassoevan", + "description": "A race condition on `useRoomIcon` -- delayed merge of rooms and subscriptions -- was causing a UI crash whenever someone tried to open a DM from the user card component.", + "milestone": "4.5.2", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "24750", + "title": "[IMPROVE] Voip Extensions disabled state", + "userLogin": "MartinSchoeler", + "milestone": "4.5.2", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "24748", + "title": "[IMPROVE] UX - VoIP Call Component", + "userLogin": "tiagoevanp", + "milestone": "4.5.3", + "contributors": [ + "tiagoevanp" + ] + }, + { + "pr": "24753", + "title": "Chore: Micro services fixes and cleanup", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24756", + "title": "Regression: Improve Sidenav open/close handling and fixed codeql configs and E2E tests", + "userLogin": "ggazzo", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "24760", + "title": "[FIX] Apple login script being loaded even when Apple Login is disabled.", + "userLogin": "pierre-lehnen-rc", + "milestone": "4.5.1", + "contributors": [ + "pierre-lehnen-rc" + ] + }, + { + "pr": "24754", + "title": "Chore: Update Livechat", + "userLogin": "MartinSchoeler", + "milestone": "4.5.1", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "24683", + "title": "[FIX] no id of room closer in livechat-close message", + "userLogin": "cuonghuunguyen", + "milestone": "4.5.1", + "contributors": [ + null + ] + }, + { + "pr": "24771", + "title": "Chore: fix grammatical errors in Features", + "userLogin": "aadishJ01", + "contributors": [ + "aadishJ01", + "web-flow" + ] + }, + { + "pr": "24759", + "title": "Chore: Fix grammatical errors in Code of Conduct", + "userLogin": "aadishJ01", + "contributors": [ + "aadishJ01", + "web-flow" + ] + }, + { + "pr": "23795", + "title": "[FIX] Reload roomslist after successful deletion of a room from admin panel.", + "userLogin": "Aman-Maheshwari", + "description": "Removed the logic for calling the `rooms.adminRooms` endPoint from the `RoomsTable` Component and moved it to its parent component `RoomsPage`.\r\nThis allows to call the endPoint `rooms.adminRooms` from `EditRoomContextBar` Component which is also has `RoomPage` Component as its parent.\r\n\r\nAlso added a succes toast message after the successful deletion of room.", + "milestone": "4.5.1", + "contributors": [ + "Aman-Maheshwari", + "web-flow", + "tassoevan" + ] + }, + { + "pr": "24743", + "title": "[FIX] System messages are sent when adding or removing a group from a team", + "userLogin": "matheusbsilva137", + "description": "- Do not send system messages when adding or removing a new or existing _group_ from a team.", + "milestone": "4.5.1", + "contributors": [ + "matheusbsilva137" + ] + }, + { + "pr": "24544", + "title": "Chore: Fix Cypress tests", + "userLogin": "rodrigok", + "contributors": [ + "rodrigok", + "tassoevan", + "dougfabris" + ] + }, + { + "pr": "24737", + "title": "[FIX] Typo and placeholder on wrap up call modal", + "userLogin": "MartinSchoeler", + "milestone": "4.5.1", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "24739", + "title": "[IMPROVE][ENTERPRISE] Don't start presence monitor when running micro services", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24738", + "title": "[FIX][ENTERPRISE] DDP streamer not sending data to all clients", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24680", + "title": "[FIX] Show only available agents on extension association modal", + "userLogin": "KevLehman", + "milestone": "4.5.1", + "contributors": [ + "KevLehman", + "tiagoevanp" + ] + }, + { + "pr": "24710", + "title": "[FIX] DDP streamer errors", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24724", + "title": "[FIX][ENTERPRISE] Presence micro service logic", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24717", + "title": "i18n: Language update from LingoHub 🤖 on 2022-03-07Z", + "userLogin": "lingohub[bot]", + "contributors": [ + null, + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "24607", + "title": "[FIX] VoIP Enable/Disable setting on CallContext/CallProvider Notifications", + "userLogin": "tiagoevanp", + "milestone": "4.5.1", + "contributors": [ + "tiagoevanp", + "web-flow", + "tassoevan" + ] + }, + { + "pr": "24726", + "title": "Chore: Improve logger to allow log of `unknown` values", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24677", + "title": "[FIX] Components for user search", + "userLogin": "juliajforesti", + "milestone": "4.5.1", + "contributors": [ + "juliajforesti", + "tassoevan", + "web-flow" + ] + }, + { + "pr": "24542", + "title": "[FIX] Date Message Export Filter Fix", + "userLogin": "eduardofcabrera", + "description": "Fix message export filter to get all messages between \"from date\" and \"to date\", including \"to date\".", + "contributors": [ + "eduardofcabrera", + "web-flow" + ] + }, + { + "pr": "24709", + "title": "[FIX] API Error preventing adding an email to users without one (like bot/app users)", + "userLogin": "debdutdeb", + "contributors": [ + "debdutdeb" + ] + }, + { + "pr": "24716", + "title": "Bump ts-node from 10.6.0 to 10.7.0 in /ee/server/services", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24476", + "title": "[FIX] Nextcloud OAuth for incomplete token URL", + "userLogin": "debdutdeb", + "milestone": "4.6.0", + "contributors": [ + "debdutdeb" + ] + }, + { + "pr": "24657", + "title": "[FIX] Voip Stream Reinitialization Error", + "userLogin": "amolghode1981", + "milestone": "4.5.1", + "contributors": [ + "amolghode1981" + ] + }, + { + "pr": "24698", + "title": "Bump pino-pretty from 7.5.2 to 7.5.3 in /ee/server/services", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24696", + "title": "[FIX] Room's message count not being incremented on import", + "userLogin": "matheusbsilva137", + "description": "- Fix rooms' message counter not being incremented on message import.", + "milestone": "4.5.1", + "contributors": [ + "matheusbsilva137" + ] + }, + { + "pr": "23824", + "title": "Chore: Improvements on role syncing (ldap, oauth and saml)", + "userLogin": "pierre-lehnen-rc", + "contributors": [ + "pierre-lehnen-rc", + "tassoevan" + ] + }, + { + "pr": "24689", + "title": "Bump pino-pretty from 7.5.1 to 7.5.2 in /ee/server/services", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24674", + "title": "[FIX] Missing username on messages imported from Slack", + "userLogin": "matheusbsilva137", + "description": "- Fix missing sender's username on messages imported from Slack.", + "milestone": "4.5.1", + "contributors": [ + "matheusbsilva137" + ] + }, + { + "pr": "24642", + "title": "Bump actions/setup-node from 2 to 3", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24644", + "title": "i18n: Language update from LingoHub 🤖 on 2022-02-28Z", + "userLogin": "lingohub[bot]", + "contributors": [ + null, + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "24590", + "title": "[FIX] Duplicated 'name' log key", + "userLogin": "sampaiodiego", + "milestone": "4.5.1", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24668", + "title": "Bump actions/checkout from 2 to 3", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24574", + "title": "Chore(deps-dev): Bump @types/mock-require from 2.0.0 to 2.0.1", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24667", + "title": "Bump ts-node from 10.5.0 to 10.6.0 in /ee/server/services", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24666", + "title": "Bump @types/ws from 8.2.3 to 8.5.2 in /ee/server/services", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24640", + "title": "Bump url-parse from 1.5.7 to 1.5.10", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "24653", + "title": "Merge master into develop & Set version to 4.6.0-develop", + "userLogin": "pierre-lehnen-rc", + "contributors": [ + "pierre-lehnen-rc", + "web-flow" + ] + }, + { + "pr": "24661", + "title": "[FIX] Typo in wrap-up term", + "userLogin": "renatobecker", + "milestone": "4.5.1", + "contributors": [ + "renatobecker" + ] + }, + { + "pr": "24028", + "title": "[IMPROVE] Updated links in readme", + "userLogin": "aswinidev", + "contributors": [ + "aswinidev", + "web-flow", + "debdutdeb" + ] + } + ] + }, + "4.5.4": { + "node_version": "14.18.3", + "npm_version": "6.14.15", + "apps_engine_version": "1.31.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "24938", + "title": "Release 4.5.4", + "userLogin": "AllanPazRibeiro", + "contributors": [ + "geekgonecrazy", + "AllanPazRibeiro" + ] + }, + { + "pr": "24930", + "title": "[FIX] SAML Force name to string", + "userLogin": "geekgonecrazy", + "milestone": "4.5.4", + "contributors": [ + "geekgonecrazy", + "web-flow", + "pierre-lehnen-rc" + ] + } + ] + }, + "4.6.0-rc.1": { + "node_version": "14.18.3", + "npm_version": "6.14.15", + "apps_engine_version": "1.31.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "24320", + "title": "[FIX] LDAP avatars being rotated according to metadata even if the setting to rotate uploads is off", + "userLogin": "matheusbsilva137", + "description": "- Use the `FileUpload_RotateImages` setting (**Administration > File Upload > Rotate images on upload**) to control whether avatars should be rotated automatically based on their data (XEIF);\r\n- Display the avatar image preview (orientation) according to the `FileUpload_RotateImages` setting.", + "milestone": "4.6.0", + "contributors": [ + "matheusbsilva137", + "web-flow", + "pierre-lehnen-rc" + ] + }, + { + "pr": "24930", + "title": "[FIX] SAML Force name to string", + "userLogin": "geekgonecrazy", + "milestone": "4.5.4", + "contributors": [ + "geekgonecrazy", + "web-flow", + "pierre-lehnen-rc" + ] + }, + { + "pr": "24908", + "title": "Regression: Call doesn't stop ringing after agent unregistration", + "userLogin": "MartinSchoeler", + "milestone": "4.6.0", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "24920", + "title": "Regression: Fix account service login expiration", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24867", + "title": "[FIX] Duplicated \"jump to message\" button on starred messages", + "userLogin": "Himanshu664", + "contributors": [ + "Himanshu664" + ] + }, + { + "pr": "24860", + "title": "[FIX] External search providers not working", + "userLogin": "tkurz", + "contributors": [ + "tkurz" + ] + } + ] + }, + "4.6.0-rc.2": { + "node_version": "14.18.3", + "npm_version": "6.14.15", + "apps_engine_version": "1.31.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "24955", + "title": "[FIX] room message not load when is a new message", + "userLogin": "filipemarins", + "description": "When the room object is searched for the first time, it does not exist on the front object yet (subscription), adding a fallback search for room list will guarantee to search the room details.\r\n\r\nbefore:\r\nhttps://user-images.githubusercontent.com/9275105/160223241-d2319f3e-82c5-47d6-867f-695ab2361a17.mp4\r\n\r\nafter:\r\nhttps://user-images.githubusercontent.com/9275105/160223244-84d0d2a1-3d95-464d-8b8a-e264b0d4d690.mp4", + "milestone": "4.5.5", + "contributors": [ + "filipemarins", + "pierre-lehnen-rc" + ] + }, + { + "pr": "24969", + "title": "Chore: Storybook mocking and examples improved", + "userLogin": "tassoevan", + "description": "- Stories from `ee/` included;\r\n- Differentiate root story kinds;\r\n- Mocking of `ServerContext` via Storybook parameters.", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "24990", + "title": "Chore: Update Livechat", + "userLogin": "MartinSchoeler", + "milestone": "4.5.5", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "24897", + "title": "[FIX] Room archived/unarchived system messages aren't sent when editing room settings", + "userLogin": "matheusbsilva137", + "description": "- Send the \"Room archived\" and \"Room unarchived\" system messages when editing room settings (and not only when rooms are archived/unarchived with the slash-command);\r\n- Fix the \"Hide System Messages\" option for the \"Room archived\" and \"Room unarchived\" system messages;", + "contributors": [ + "matheusbsilva137" + ] + }, + { + "pr": "24925", + "title": "Chore: add some missing REST definitions", + "userLogin": "gerzonc", + "description": "On the [mobile client](https://github.com/RocketChat/Rocket.Chat.ReactNative), we made an effort to collect more `REST API` definitions that are missing on the server side during our migration to TypeScript. Since we're both migrating to TypeScript, we thought it would be a good idea to share those so you guys can benefit from our initiative.", + "contributors": [ + "gerzonc" + ] + }, + { + "pr": "24971", + "title": "i18n: Language update from LingoHub 🤖 on 2022-03-28Z", + "userLogin": "lingohub[bot]", + "contributors": [ + null + ] + }, + { + "pr": "24921", + "title": "[FIX] Register with Secret URL", + "userLogin": "yash-rajpal", + "contributors": [ + "yash-rajpal" + ] + }, + { + "pr": "24948", + "title": "Regression: Fix unexpected errors breaking ddp-streamer", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + } + ] + }, + "4.6.0-rc.3": { + "node_version": "14.18.3", + "npm_version": "6.14.15", + "apps_engine_version": "1.31.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "24994", + "title": "[FIX] High CPU usage caused by CallProvider", + "userLogin": "tiagoevanp", + "description": "Remove infinity loop inside useVoipClient hook.\r\n\r\n#closes #24970", + "milestone": "4.5.5", + "contributors": [ + "ggazzo", + "tiagoevanp" + ] + } + ] + }, + "4.5.5": { + "node_version": "14.18.3", + "npm_version": "6.14.15", + "apps_engine_version": "1.31.0", "mongo_versions": [ "3.6", "4.0", @@ -70006,81 +72889,184 @@ ], "pull_requests": [ { - "pr": "24432", - "title": "Release 4.4.1", - "userLogin": "pierre-lehnen-rc", + "pr": "24998", + "title": "Release 4.5.5", + "userLogin": "sampaiodiego", "contributors": [ + "MartinSchoeler", "sampaiodiego", - "pierre-lehnen-rc", - "dougfabris", - "ostjen" + "filipemarins", + "tiagoevanp" ] }, { - "pr": "24387", - "title": "[FIX] Slash commands previews not working", - "userLogin": "ostjen", - "milestone": "4.4.1", + "pr": "24994", + "title": "[FIX] High CPU usage caused by CallProvider", + "userLogin": "tiagoevanp", + "description": "Remove infinity loop inside useVoipClient hook.\r\n\r\n#closes #24970", + "milestone": "4.5.5", "contributors": [ - "ostjen" + "ggazzo", + "tiagoevanp" ] }, { - "pr": "24381", - "title": "[FIX] Add ?close to OAuth callback url", - "userLogin": "sampaiodiego", - "milestone": "4.4.1", + "pr": "24955", + "title": "[FIX] Multiple issues starting a new DM", + "userLogin": "filipemarins", + "description": "When the room object is searched for the first time, it does not exist on the front object yet (subscription), adding a fallback search for room list will guarantee to search the room details.\r\n\r\nbefore:\r\nhttps://user-images.githubusercontent.com/9275105/160223241-d2319f3e-82c5-47d6-867f-695ab2361a17.mp4\r\n\r\nafter:\r\nhttps://user-images.githubusercontent.com/9275105/160223244-84d0d2a1-3d95-464d-8b8a-e264b0d4d690.mp4", + "milestone": "4.5.5", "contributors": [ - "sampaiodiego" + "filipemarins", + "pierre-lehnen-rc" ] }, { - "pr": "24409", - "title": "[FIX] Startup errors creating indexes", - "userLogin": "sampaiodiego", - "description": "Fix `bio` and `prid` startup index creation errors.", - "milestone": "4.4.1", + "pr": "24990", + "title": "Chore: Update Livechat", + "userLogin": "MartinSchoeler", + "milestone": "4.5.5", "contributors": [ - "sampaiodiego" + "MartinSchoeler" ] - }, + } + ] + }, + "4.6.0-rc.4": { + "node_version": "14.18.3", + "npm_version": "6.14.15", + "apps_engine_version": "1.31.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ { - "pr": "24401", - "title": "[FIX] Outgoing webhook without scripts not saving messages", + "pr": "25017", + "title": "Regression: Add createdOTR index", "userLogin": "sampaiodiego", - "milestone": "4.4.1", "contributors": [ "sampaiodiego" ] }, { - "pr": "24407", - "title": "[FIX] Skip cloud steps for registered servers on setup wizard", + "pr": "25015", + "title": "Chore: Bump Fuselage packages", "userLogin": "dougfabris", - "milestone": "4.4.1", + "description": "It uses the last stable version of Fuselage packages.", + "milestone": "4.6.0", "contributors": [ "dougfabris", - "tassoevan", + "tassoevan" + ] + }, + { + "pr": "24999", + "title": "Regression: Custom roles displaying ID instead of name on some admin screens", + "userLogin": "pierre-lehnen-rc", + "description": "![image](https://user-images.githubusercontent.com/55164754/160981416-555bcaa1-c075-4260-937c-64523472da43.png)\r\n![image](https://user-images.githubusercontent.com/55164754/160981452-6eae4e74-8425-4073-8256-472aba72b9db.png)", + "milestone": "4.6.0", + "contributors": [ + "pierre-lehnen-rc", + "dougfabris", + "web-flow" + ] + }, + { + "pr": "24835", + "title": "[NEW] Upgrade Tab", + "userLogin": "gabriellsh", + "description": "![image](https://user-images.githubusercontent.com/27704687/160172260-c656282e-a487-4092-948d-d11c9bacb598.png)", + "milestone": "4.6.0", + "contributors": [ "gabriellsh", + "dougfabris", + "web-flow", + "tassoevan", + "pierre-lehnen-rc" + ] + }, + { + "pr": "24980", + "title": "Regression: Error is raised when there's no Asterisk queue available yet", + "userLogin": "amolghode1981", + "milestone": "4.7.0", + "contributors": [ + "amolghode1981" + ] + } + ] + }, + "4.6.0-rc.5": { + "node_version": "14.18.3", + "npm_version": "6.14.15", + "apps_engine_version": "1.31.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "25021", + "title": "Bump @rocket.chat/emitter from 0.31.4 to 0.31.9 in /ee/server/services", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", "web-flow" ] }, { - "pr": "24418", - "title": "[FIX] Oembed request not respecting payload limit", - "userLogin": "sampaiodiego", - "milestone": "4.4.1", + "pr": "25020", + "title": "Bump @rocket.chat/ui-kit from 0.31.4 to 0.31.9 in /ee/server/services", + "userLogin": "dependabot[bot]", "contributors": [ - "sampaiodiego", + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "25019", + "title": "Bump @rocket.chat/message-parser from 0.31.4 to 0.31.9 in /ee/server/services", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", + "web-flow" + ] + }, + { + "pr": "25018", + "title": "Bump @rocket.chat/string-helpers from 0.31.4 to 0.31.9 in /ee/server/services", + "userLogin": "dependabot[bot]", + "contributors": [ + "dependabot[bot]", "web-flow" ] } ] }, - "4.4.2": { - "node_version": "14.18.2", + "4.6.0": { + "node_version": "14.18.3", "npm_version": "6.14.15", - "apps_engine_version": "1.30.0", + "apps_engine_version": "1.31.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [] + }, + "4.6.1": { + "node_version": "14.18.3", + "npm_version": "6.14.15", + "apps_engine_version": "1.31.0", "mongo_versions": [ "3.6", "4.0", @@ -70090,24 +73076,189 @@ ], "pull_requests": [ { - "pr": "24450", - "title": "[FIX] OAuth mismatch redirect_uri error", + "pr": "25022", + "title": "[FIX] Proxy settings being ignored", + "userLogin": "pierre-lehnen-rc", + "description": "Modify Meteor's `HTTP.call` to add back proxy support", + "milestone": "4.6.1", + "contributors": [ + "pierre-lehnen-rc", + "sampaiodiego" + ] + }, + { + "pr": "25082", + "title": "[FIX] Invitation links don't redirect to the registration form", + "userLogin": "yash-rajpal", + "milestone": "4.6.1", + "contributors": [ + "yash-rajpal" + ] + }, + { + "pr": "25069", + "title": "[FIX] FormData uploads not working", + "userLogin": "gabriellsh", + "milestone": "4.6.1", + "contributors": [ + "gabriellsh", + "dougfabris" + ] + }, + { + "pr": "25067", + "title": "[FIX] NPS never finishing sending results", "userLogin": "sampaiodiego", - "milestone": "4.4.2", + "milestone": "4.6.1", "contributors": [ "sampaiodiego" ] }, { - "pr": "24453", - "title": "Chore: bump fuselage version", + "pr": "25050", + "title": "[FIX] Upgrade Tab showing for a split second", + "userLogin": "gabriellsh", + "milestone": "4.6.1", + "contributors": [ + "gabriellsh" + ] + }, + { + "pr": "25055", + "title": "[FIX] UserAutoComplete not rendering UserAvatar correctly", "userLogin": "dougfabris", - "milestone": "4.4.2", + "description": "### before\r\n![Screen Shot 2022-04-04 at 16 50 21](https://user-images.githubusercontent.com/27704687/161620921-800bf66a-806d-4f83-b2e1-073c34215001.png)\r\n\r\n### after\r\n![Screen Shot 2022-04-04 at 16 49 00](https://user-images.githubusercontent.com/27704687/161620720-3e27774d-c241-46ca-b764-932a9295d709.png)", + "milestone": "4.6.1", "contributors": [ "dougfabris" ] } ] + }, + "4.4.3": { + "node_version": "14.18.2", + "npm_version": "6.14.15", + "apps_engine_version": "1.30.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "25022", + "title": "[FIX] Proxy settings being ignored", + "userLogin": "pierre-lehnen-rc", + "description": "Modify Meteor's `HTTP.call` to add back proxy support", + "milestone": "4.6.1", + "contributors": [ + "pierre-lehnen-rc", + "sampaiodiego" + ] + }, + { + "pr": "25067", + "title": "[FIX] NPS never finishing sending results", + "userLogin": "sampaiodiego", + "milestone": "4.6.1", + "contributors": [ + "sampaiodiego" + ] + } + ] + }, + "4.5.6": { + "node_version": "14.18.3", + "npm_version": "6.14.15", + "apps_engine_version": "1.31.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "25022", + "title": "[FIX] Proxy settings being ignored", + "userLogin": "pierre-lehnen-rc", + "description": "Modify Meteor's `HTTP.call` to add back proxy support", + "milestone": "4.6.1", + "contributors": [ + "pierre-lehnen-rc", + "sampaiodiego" + ] + }, + { + "pr": "25067", + "title": "[FIX] NPS never finishing sending results", + "userLogin": "sampaiodiego", + "milestone": "4.6.1", + "contributors": [ + "sampaiodiego" + ] + } + ] + }, + "4.6.2": { + "node_version": "14.18.3", + "npm_version": "6.14.15", + "apps_engine_version": "1.31.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "25101", + "title": "[FIX] Database indexes not being created", + "userLogin": "sampaiodiego", + "milestone": "4.6.2", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "24933", + "title": "[FIX] Deactivating user breaks if user is the only room owner", + "userLogin": "sidmohanty11", + "description": "## Before\r\n\r\nhttps://user-images.githubusercontent.com/73601258/160000871-cfc2f2a5-2a59-4d27-8049-7754d003dd48.mp4\r\n\r\n\r\n\r\n## After\r\nhttps://user-images.githubusercontent.com/73601258/159998287-681ab475-ff33-4282-82ff-db751c59a392.mp4", + "milestone": "4.6.2", + "contributors": [ + "sidmohanty11", + "sampaiodiego" + ] + } + ] + }, + "4.6.3": { + "node_version": "14.18.3", + "npm_version": "6.14.15", + "apps_engine_version": "1.31.0", + "mongo_versions": [ + "3.6", + "4.0", + "4.2", + "4.4", + "5.0" + ], + "pull_requests": [ + { + "pr": "25220", + "title": "[FIX] Desktop notification on multi-instance environments", + "userLogin": "sampaiodiego", + "milestone": "4.6.3", + "contributors": [ + "sampaiodiego" + ] + } + ] } } } \ No newline at end of file diff --git a/.github/no-js-action-config.json b/.github/no-js-action-config.json index 435d5ace554c..f2c55418f002 100644 --- a/.github/no-js-action-config.json +++ b/.github/no-js-action-config.json @@ -1,8 +1,5 @@ { "added": { - "ignore": [ - "packages/accounts-linkedin/**/*", - "packages/linkedin-oauth/**/*" - ] + "ignore": ["packages/accounts-linkedin/**/*", "packages/linkedin-oauth/**/*", "tests/cypress/integration/08-resolutions.spec.js"] } } diff --git a/.github/pr-title-checker-config.json b/.github/pr-title-checker-config.json index eb103217f78d..3b36e2782f39 100644 --- a/.github/pr-title-checker-config.json +++ b/.github/pr-title-checker-config.json @@ -4,7 +4,7 @@ "color": "B60205" }, "CHECKS": { - "regexp": "^(?:(?:\\[(NEW|BREAK|IMPROVE|FIX|Upstream Catchup)\\](\\[(ENTERPRISE|APPS)\\])?|(?:Regression|Chore|Revert|i18n):)|(?:Bump) .+|Release [0-9]+\\.[0-9]+\\.[0-9]+|Merge master into develop)", + "regexp": "^(?:(?:\\[(NEW|BREAK|IMPROVE|FIX|Upstream Catchup)\\](\\[(ENTERPRISE|APPS)\\])?|(?:Regression|Chore|Revert|i18n):)|(?:Bump)) .+$|^Release [0-9]+\\.[0-9]+\\.[0-9]+$|^Merge master into develop", "ignoreLabels" : ["[ignore-title]"] } } diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index f89bea76abce..8553cb26b770 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -19,166 +19,147 @@ jobs: runs-on: ubuntu-20.04 steps: + - name: Github Info + run: | + echo "GITHUB_ACTION: $GITHUB_ACTION" + echo "GITHUB_ACTOR: $GITHUB_ACTOR" + echo "GITHUB_REF: $GITHUB_REF" + echo "GITHUB_HEAD_REF: $GITHUB_HEAD_REF" + echo "GITHUB_BASE_REF: $GITHUB_BASE_REF" + echo "github.event_name: ${{ github.event_name }}" + cat $GITHUB_EVENT_PATH + + - name: Use Node.js 14.18.3 + uses: actions/setup-node@v3 + with: + node-version: '14.18.3' + + - uses: actions/checkout@v3 + + - name: Free disk space + run: | + sudo swapoff -a + sudo rm -f /swapfile + sudo apt clean + docker rmi $(docker image ls -aq) + df -h + + - name: check package-lock + run: | + npx package-lock-check + + - name: Cache cypress + id: cache-cypress + uses: actions/cache@v2 + with: + path: /home/runner/.cache/Cypress + key: ${{ runner.OS }}-cache-cypress-${{ hashFiles('**/package-lock.json', '.github/workflows/build_and_test.yml') }} + + # - name: Cache node modules + # id: cache-nodemodules + # uses: actions/cache@v2 + # with: + # path: | + # ./node_modules + # ./ee/server/services/node_modules + # key: ${{ runner.OS }}-node_modules-4-${{ hashFiles('**/package-lock.json', '.github/workflows/build_and_test.yml') }} + + - name: Cache meteor local + uses: actions/cache@v2 + with: + path: ./.meteor/local + key: ${{ runner.OS }}-meteor_cache-${{ hashFiles('.meteor/versions', '.github/workflows/build_and_test.yml') }} + + - name: Cache meteor + uses: actions/cache@v2 + with: + path: ~/.meteor + key: ${{ runner.OS }}-meteor-${{ hashFiles('.meteor/release', '.github/workflows/build_and_test.yml') }} + + - name: Install Meteor + run: | + # Restore bin from cache + set +e + METEOR_SYMLINK_TARGET=$(readlink ~/.meteor/meteor) + METEOR_TOOL_DIRECTORY=$(dirname "$METEOR_SYMLINK_TARGET") + set -e + LAUNCHER=$HOME/.meteor/$METEOR_TOOL_DIRECTORY/scripts/admin/launch-meteor + if [ -e $LAUNCHER ] + then + echo "Cached Meteor bin found, restoring it" + sudo cp "$LAUNCHER" "/usr/local/bin/meteor" + else + echo "No cached Meteor bin found." + fi - - name: Github Info - run: | - echo "GITHUB_ACTION: $GITHUB_ACTION" - echo "GITHUB_ACTOR: $GITHUB_ACTOR" - echo "GITHUB_REF: $GITHUB_REF" - echo "GITHUB_HEAD_REF: $GITHUB_HEAD_REF" - echo "GITHUB_BASE_REF: $GITHUB_BASE_REF" - echo "github.event_name: ${{ github.event_name }}" - cat $GITHUB_EVENT_PATH - - - name: Use Node.js 14.18.2 - uses: actions/setup-node@v2 - with: - node-version: "14.18.2" - - - uses: actions/checkout@v2 - - - name: Free disk space - run: | - sudo swapoff -a - sudo rm -f /swapfile - sudo apt clean - docker rmi $(docker image ls -aq) - df -h - - - name: check package-lock - run: | - npx package-lock-check - - - name: Cache cypress - id: cache-cypress - uses: actions/cache@v2 - with: - path: /home/runner/.cache/Cypress - key: ${{ runner.OS }}-cache-cypress-${{ hashFiles('**/package-lock.json', '.github/workflows/build_and_test.yml') }} - - # - name: Cache node modules - # id: cache-nodemodules - # uses: actions/cache@v2 - # with: - # path: | - # ./node_modules - # ./ee/server/services/node_modules - # key: ${{ runner.OS }}-node_modules-4-${{ hashFiles('**/package-lock.json', '.github/workflows/build_and_test.yml') }} - - - name: Cache meteor local - uses: actions/cache@v2 - with: - path: ./.meteor/local - key: ${{ runner.OS }}-meteor_cache-${{ hashFiles('.meteor/versions', '.github/workflows/build_and_test.yml') }} - - - name: Cache meteor - uses: actions/cache@v2 - with: - path: ~/.meteor - key: ${{ runner.OS }}-meteor-${{ hashFiles('.meteor/release', '.github/workflows/build_and_test.yml') }} - - - name: Install Meteor - run: | - # Restore bin from cache - set +e - METEOR_SYMLINK_TARGET=$(readlink ~/.meteor/meteor) - METEOR_TOOL_DIRECTORY=$(dirname "$METEOR_SYMLINK_TARGET") - set -e - LAUNCHER=$HOME/.meteor/$METEOR_TOOL_DIRECTORY/scripts/admin/launch-meteor - if [ -e $LAUNCHER ] - then - echo "Cached Meteor bin found, restoring it" - sudo cp "$LAUNCHER" "/usr/local/bin/meteor" - else - echo "No cached Meteor bin found." - fi - - # only install meteor if bin isn't found - command -v meteor >/dev/null 2>&1 || curl https://install.meteor.com | sed s/--progress-bar/-sL/g | /bin/sh - - - name: Versions - run: | - npm --versions - node -v - meteor --version - meteor npm --versions - meteor node -v - git version - - - name: npm install - # if: steps.cache-nodemodules.outputs.cache-hit != 'true' || steps.cache-cypress.outputs.cache-hit != 'true' - run: | - meteor npm install - cd ./ee/server/services - npm install - cd - - - - run: meteor npm run lint - - - run: meteor npm run translation-check - - - run: meteor npm run typecheck - - - name: Build Storybook to sanity check components - run: npm run build-storybook ; rm -rf ./storybook-static - - - # To reduce memory need during actual build, build the packages solely first - # - name: Build a Meteor cache - # run: | - # # to do this we can clear the main files and it build the rest - # echo "" > server/main.ts - # echo "" > client/main.ts - # sed -i.backup 's/rocketchat:livechat/#rocketchat:livechat/' .meteor/packages - # meteor build --server-only --debug --directory /tmp/build-temp - # git checkout -- server/main.ts client/main.ts .meteor/packages - - - name: Reset Meteor - if: startsWith(github.ref, 'refs/tags/') == 'true' || github.ref == 'refs/heads/develop' - run: | - meteor reset - - - name: Try building micro services - run: | - cd ./ee/server/services - npm run build - # check if build succeeded - [ ! -d ./dist/ee/server/services ] && exit 1 - rm -rf dist/ - - - name: Build Rocket.Chat From Pull Request - if: startsWith(github.ref, 'refs/pull/') == true - env: - METEOR_PROFILE: 1000 - run: | - meteor build --server-only --directory --debug /tmp/build-test - - - name: Build Rocket.Chat - if: startsWith(github.ref, 'refs/pull/') != true - run: | - meteor build --server-only --directory /tmp/build-test - - - name: Prepare build - run: | - mkdir /tmp/build/ - cd /tmp/build-test - tar czf /tmp/build/Rocket.Chat.tar.gz bundle - cd /tmp/build-test/bundle/programs/server - npm install - cd /tmp - tar czf Rocket.Chat.test.tar.gz ./build-test - - - name: Store build for tests - uses: actions/upload-artifact@v2 - with: - name: build-test - path: /tmp/Rocket.Chat.test.tar.gz - - - name: Store build - uses: actions/upload-artifact@v2 - with: - name: build - path: /tmp/build + # only install meteor if bin isn't found + command -v meteor >/dev/null 2>&1 || curl https://install.meteor.com | sed s/--progress-bar/-sL/g | /bin/sh + + - name: Versions + run: | + npm --versions + node -v + meteor --version + meteor npm --versions + meteor node -v + git version + + - name: npm install + # if: steps.cache-nodemodules.outputs.cache-hit != 'true' || steps.cache-cypress.outputs.cache-hit != 'true' + run: | + meteor npm install + cd ./ee/server/services + npm install + cd - + + - run: meteor npm run lint + + - run: meteor npm run translation-check + + - name: TS typecheck + run: | + meteor npm run typecheck + cd ./ee/server/services + meteor npm run typecheck + + - name: Reset Meteor + if: startsWith(github.ref, 'refs/tags/') == 'true' || github.ref == 'refs/heads/develop' + run: | + meteor reset + + - name: Build Rocket.Chat From Pull Request + if: startsWith(github.ref, 'refs/pull/') == true + env: + METEOR_PROFILE: 1000 + run: | + meteor build --server-only --directory --debug /tmp/build-test + + - name: Build Rocket.Chat + if: startsWith(github.ref, 'refs/pull/') != true + run: | + meteor build --server-only --directory /tmp/build-test + + - name: Prepare build + run: | + mkdir /tmp/build/ + cd /tmp/build-test + tar czf /tmp/build/Rocket.Chat.tar.gz bundle + cd /tmp/build-test/bundle/programs/server + npm install --production + cd /tmp + tar czf Rocket.Chat.test.tar.gz ./build-test + + - name: Store build for tests + uses: actions/upload-artifact@v2 + with: + name: build-test + path: /tmp/Rocket.Chat.test.tar.gz + + - name: Store build + uses: actions/upload-artifact@v2 + with: + name: build + path: /tmp/build test: runs-on: ubuntu-latest @@ -186,96 +167,239 @@ jobs: strategy: matrix: - node-version: ["14.18.2"] - mongodb-version: ["3.6", "4.0", "4.2", "4.4","5.0"] + node-version: ['14.18.3'] + mongodb-version: ['3.6', '4.0', '4.2', '4.4', '5.0'] steps: - - name: Launch MongoDB - uses: wbari/start-mongoDB@v0.2 - with: - mongoDBVersion: ${{ matrix.mongodb-version }} --replSet=rs0 - - - name: Restore build for tests - uses: actions/download-artifact@v2 - with: - name: build-test - path: /tmp - - - name: Decompress build - run: | - cd /tmp - tar xzf Rocket.Chat.test.tar.gz - cd - - - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v2 - with: - node-version: ${{ matrix.node-version }} - - - name: Setup Chrome - run: | - npm i chromedriver - - - name: Configure Replica Set - run: | - docker exec mongo mongo --eval 'rs.initiate({_id:"rs0", members: [{"_id":1, "host":"localhost:27017"}]})' - docker exec mongo mongo --eval 'rs.status()' - - - uses: actions/checkout@v2 - - - name: Cache cypress - id: cache-cypress - uses: actions/cache@v2 - with: - path: /home/runner/.cache/Cypress - key: ${{ runner.OS }}-cache-cypress-${{ hashFiles('**/package-lock.json', '.github/workflows/build_and_test.yml') }} - - # - name: Cache node modules - # id: cache-nodemodules - # uses: actions/cache@v2 - # with: - # path: | - # ./node_modules - # ./ee/server/services/node_modules - # key: ${{ runner.OS }}-node_modules-4-${{ hashFiles('**/package-lock.json', '.github/workflows/build_and_test.yml') }} - - - name: NPM install - # if: steps.cache-nodemodules.outputs.cache-hit != 'true' || steps.cache-cypress.outputs.cache-hit != 'true' - run: | - npm install - - - name: Unit Test (definitions) - run: npm run testunit-definition - - - name: Unit Test - run: npm run testunit + - name: Launch MongoDB + uses: wbari/start-mongoDB@v0.2 + with: + mongoDBVersion: ${{ matrix.mongodb-version }} --replSet=rs0 + + - name: Restore build for tests + uses: actions/download-artifact@v2 + with: + name: build-test + path: /tmp + + - name: Decompress build + run: | + cd /tmp + tar xzf Rocket.Chat.test.tar.gz + cd - + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + + - name: Setup Chrome + run: | + npm i chromedriver + + - name: Configure Replica Set + run: | + docker exec mongo mongo --eval 'rs.initiate({_id:"rs0", members: [{"_id":1, "host":"localhost:27017"}]})' + docker exec mongo mongo --eval 'rs.status()' + + - uses: actions/checkout@v3 + + - name: Cache cypress + id: cache-cypress + uses: actions/cache@v2 + with: + path: /home/runner/.cache/Cypress + key: ${{ runner.OS }}-cache-cypress-${{ hashFiles('**/package-lock.json', '.github/workflows/build_and_test.yml') }} + + # - name: Cache node modules + # id: cache-nodemodules + # uses: actions/cache@v2 + # with: + # path: | + # ./node_modules + # ./ee/server/services/node_modules + # key: ${{ runner.OS }}-node_modules-4-${{ hashFiles('**/package-lock.json', '.github/workflows/build_and_test.yml') }} + + - name: NPM install + # if: steps.cache-nodemodules.outputs.cache-hit != 'true' || steps.cache-cypress.outputs.cache-hit != 'true' + run: | + npm install + + - name: Unit Test (definitions) + run: npm run testunit-definition + + - name: Unit Test + run: npm run testunit + + - name: Unit Test (client) + run: npm run testunit-client + + - name: E2E Test API + env: + TEST_MODE: 'true' + MONGO_URL: mongodb://localhost:27017/rocketchat + MONGO_OPLOG_URL: mongodb://localhost:27017/local + run: | + echo -e 'pcm.!default {\n type hw\n card 0\n}\n\nctl.!default {\n type hw\n card 0\n}' > ~/.asoundrc + Xvfb -screen 0 1024x768x24 :99 & + for i in $(seq 1 5); do (docker exec mongo mongo rocketchat --eval 'db.dropDatabase()') && npm run testci -- --test=testapi && s=0 && break || s=$? && sleep 1; done; (exit $s) + +# Disabling tests for now + # - name: E2E Test UI + # env: + # TEST_MODE: 'true' + # MONGO_URL: mongodb://localhost:27017/rocketchat + # MONGO_OPLOG_URL: mongodb://localhost:27017/local + # run: | + # echo -e 'pcm.!default {\n type hw\n card 0\n}\n\nctl.!default {\n type hw\n card 0\n}' > ~/.asoundrc + # Xvfb -screen 0 1024x768x24 :99 & + # for i in $(seq 1 2); do (docker exec mongo mongo rocketchat --eval 'db.dropDatabase()') && npm run testci -- --test=testui && s=0 && break || s=$? && ([ ! -w tests/cypress/screenshots ] || mv tests/cypress/screenshots tests/cypress/screenshots-$i) && ([ ! -w tests/cypress/videos ] || mv tests/cypress/videos tests/cypress/videos-$i) && sleep 1; done; (exit $s) + + # - name: Store cypress test screenshots + # uses: actions/upload-artifact@v2 + # if: failure() + # with: + # name: cypress-test-screenshots + # path: tests/cypress/screenshots* + + # - name: Store cypress test videos + # uses: actions/upload-artifact@v2 + # if: failure() + # with: + # name: cypress-test-videos + # path: tests/cypress/videos* + + test-ee: + runs-on: ubuntu-20.04 + needs: build - - name: Unit Test (client) - run: npm run testunit-client + strategy: + matrix: + node-version: ['14.18.3'] + mongodb-version: ['4.4'] - - name: E2E Test - env: - TEST_MODE: "true" - MONGO_URL: mongodb://localhost:27017/rocketchat - MONGO_OPLOG_URL: mongodb://localhost:27017/local - run: | - echo -e 'pcm.!default {\n type hw\n card 0\n}\n\nctl.!default {\n type hw\n card 0\n}' > ~/.asoundrc - Xvfb -screen 0 1024x768x24 :99 & - for i in $(seq 1 5); do (docker exec mongo mongo rocketchat --eval 'db.dropDatabase()') && npm run testci && s=0 && break || s=$? && sleep 1; done; (exit $s) - -# notification: -# runs-on: ubuntu-20.04 -# needs: test - -# steps: -# - name: Rocket.Chat Notification -# uses: RocketChat/Rocket.Chat.GitHub.Action.Notification@1.1.1 -# with: -# type: ${{ job.status }} -# job_name: '**Build and Test**' -# url: ${{ secrets.ROCKETCHAT_WEBHOOK }} -# commit: true -# token: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Launch MongoDB + uses: wbari/start-mongoDB@v0.2 + with: + mongoDBVersion: ${{ matrix.mongodb-version }} --replSet=rs0 + + - name: Launch NATS + run: sudo docker run --name nats -d -p 4222:4222 nats:2.4 + + - name: Restore build for tests + uses: actions/download-artifact@v2 + with: + name: build-test + path: /tmp + + - name: Decompress build + run: | + cd /tmp + tar xzf Rocket.Chat.test.tar.gz + cd - + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + + - name: Setup Chrome + run: | + npm i chromedriver + + - name: Configure Replica Set + run: | + docker exec mongo mongo --eval 'rs.initiate({_id:"rs0", members: [{"_id":1, "host":"localhost:27017"}]})' + docker exec mongo mongo --eval 'rs.status()' + + - uses: actions/checkout@v3 + + - name: Cache cypress + id: cache-cypress + uses: actions/cache@v2 + with: + path: /home/runner/.cache/Cypress + key: ${{ runner.OS }}-cache-cypress-${{ hashFiles('**/package-lock.json', '.github/workflows/build_and_test.yml') }} + + # - name: Cache node modules + # id: cache-nodemodules + # uses: actions/cache@v2 + # with: + # path: | + # ./node_modules + # ./ee/server/services/node_modules + # key: ${{ runner.OS }}-node_modules-4-${{ hashFiles('**/package-lock.json', '.github/workflows/build_and_test.yml') }} + + - name: NPM install + # if: steps.cache-nodemodules.outputs.cache-hit != 'true' || steps.cache-cypress.outputs.cache-hit != 'true' + run: | + npm install + cd ./ee/server/services + npm install + + - name: Build micro services + run: | + cd ./ee/server/services + npm run build + + - name: E2E Test API + env: + TEST_MODE: 'true' + MONGO_URL: mongodb://localhost:27017/rocketchat + MONGO_OPLOG_URL: mongodb://localhost:27017/local + ENTERPRISE_LICENSE: ${{ secrets.ENTERPRISE_LICENSE }} + TRANSPORTER: nats://localhost:4222 + SKIP_PROCESS_EVENT_REGISTRATION: 'true' + run: | + echo -e 'pcm.!default {\n type hw\n card 0\n}\n\nctl.!default {\n type hw\n card 0\n}' > ~/.asoundrc + Xvfb -screen 0 1024x768x24 :99 & + + for i in $(seq 1 5); do (docker exec mongo mongo rocketchat --eval 'db.dropDatabase()') && npm run testci -- --enterprise --test=testapi && s=0 && break || s=$? && sleep 1; done; (exit $s) + # Disabling tests for now + # - name: E2E Test UI + # env: + # TEST_MODE: 'true' + # MONGO_URL: mongodb://localhost:27017/rocketchat + # MONGO_OPLOG_URL: mongodb://localhost:27017/local + # ENTERPRISE_LICENSE: ${{ secrets.ENTERPRISE_LICENSE }} + # TRANSPORTER: nats://localhost:4222 + # CYPRESS_BASE_URL: http://localhost:4000 + # CYPRESS_TEST_API_URL: http://localhost:4000 + # OVERWRITE_SETTING_Site_Url: http://localhost:4000 + # SKIP_PROCESS_EVENT_REGISTRATION: 'true' + # run: | + # echo -e 'pcm.!default {\n type hw\n card 0\n}\n\nctl.!default {\n type hw\n card 0\n}' > ~/.asoundrc + # Xvfb -screen 0 1024x768x24 :99 & + + # for i in $(seq 1 2); do (docker exec mongo mongo rocketchat --eval 'db.dropDatabase()') && npm run testci -- --enterprise --test=testui && s=0 && break || s=$? && ([ ! -w tests/cypress/screenshots ] || mv tests/cypress/screenshots tests/cypress/screenshots-$i) && ([ ! -w tests/cypress/videos ] || mv tests/cypress/videos tests/cypress/videos-$i) && sleep 1; done; (exit $s) + + # - name: Store cypress test screenshots + # uses: actions/upload-artifact@v2 + # if: failure() + # with: + # name: ee-cypress-test-screenshots + # path: tests/cypress/screenshots* + + # - name: Store cypress test videos + # uses: actions/upload-artifact@v2 + # if: failure() + # with: + # name: ee-cypress-test-videos + # path: tests/cypress/videos* + # notification: + # runs-on: ubuntu-20.04 + # needs: test + + # steps: + # - name: Rocket.Chat Notification + # uses: RocketChat/Rocket.Chat.GitHub.Action.Notification@1.1.1 + # with: + # type: ${{ job.status }} + # job_name: '**Build and Test**' + # url: ${{ secrets.ROCKETCHAT_WEBHOOK }} + # commit: true + # token: ${{ secrets.GITHUB_TOKEN }} # build-image-pr: # runs-on: ubuntu-20.04 @@ -413,77 +537,77 @@ jobs: needs: test steps: - - uses: actions/checkout@v2 - - - name: Restore build - uses: actions/download-artifact@v2 - with: - name: build - path: /tmp/build - - - name: Publish assets - env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_DEFAULT_REGION: 'us-east-1' - GPG_PASSWORD: ${{ secrets.GPG_PASSWORD }} - REDHAT_REGISTRY_PID: ${{ secrets.REDHAT_REGISTRY_PID }} - REDHAT_REGISTRY_KEY: ${{ secrets.REDHAT_REGISTRY_KEY }} - UPDATE_TOKEN: ${{ secrets.UPDATE_TOKEN }} - run: | - if [[ '${{ github.event_name }}' = 'release' ]]; then - GIT_TAG="${GITHUB_REF#*tags/}" - GIT_BRANCH="" - ARTIFACT_NAME="$(npm run version --silent)" - RC_VERSION=$GIT_TAG - - if [[ $GIT_TAG =~ ^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+ ]]; then - SNAP_CHANNEL=candidate - RC_RELEASE=candidate - elif [[ $GIT_TAG =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - SNAP_CHANNEL=stable - RC_RELEASE=stable + - uses: actions/checkout@v3 + + - name: Restore build + uses: actions/download-artifact@v2 + with: + name: build + path: /tmp/build + + - name: Publish assets + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: 'us-east-1' + GPG_PASSWORD: ${{ secrets.GPG_PASSWORD }} + REDHAT_REGISTRY_PID: ${{ secrets.REDHAT_REGISTRY_PID }} + REDHAT_REGISTRY_KEY: ${{ secrets.REDHAT_REGISTRY_KEY }} + UPDATE_TOKEN: ${{ secrets.UPDATE_TOKEN }} + run: | + if [[ '${{ github.event_name }}' = 'release' ]]; then + GIT_TAG="${GITHUB_REF#*tags/}" + GIT_BRANCH="" + ARTIFACT_NAME="$(npm run version --silent)" + RC_VERSION=$GIT_TAG + + if [[ $GIT_TAG =~ ^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+ ]]; then + SNAP_CHANNEL=candidate + RC_RELEASE=candidate + elif [[ $GIT_TAG =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + SNAP_CHANNEL=stable + RC_RELEASE=stable + fi + else + GIT_TAG="" + GIT_BRANCH="${GITHUB_REF#*heads/}" + ARTIFACT_NAME="$(npm run version --silent).$GITHUB_SHA" + RC_VERSION="$(npm run version --silent)" + SNAP_CHANNEL=edge + RC_RELEASE=develop + fi; + ROCKET_DEPLOY_DIR="/tmp/deploy" + FILENAME="$ROCKET_DEPLOY_DIR/rocket.chat-$ARTIFACT_NAME.tgz"; + + aws s3 cp s3://rocketchat/sign.key.gpg .github/sign.key.gpg + + mkdir -p $ROCKET_DEPLOY_DIR + + cp .github/sign.key.gpg /tmp + gpg --yes --batch --passphrase=$GPG_PASSWORD /tmp/sign.key.gpg + gpg --allow-secret-key-import --import /tmp/sign.key + rm /tmp/sign.key + + ln -s /tmp/build/Rocket.Chat.tar.gz "$FILENAME" + gpg --armor --detach-sign "$FILENAME" + + aws s3 cp $ROCKET_DEPLOY_DIR/ s3://download.rocket.chat/build/ --recursive + + curl -H "Content-Type: application/json" -H "X-Update-Token: $UPDATE_TOKEN" -d \ + "{\"nodeVersion\": \"14.18.3\", \"compatibleMongoVersions\": [\"3.6\", \"4.0\", \"4.2\", \"4.4\", \"5.0\"], \"commit\": \"$GITHUB_SHA\", \"tag\": \"$RC_VERSION\", \"branch\": \"$GIT_BRANCH\", \"artifactName\": \"$ARTIFACT_NAME\", \"releaseType\": \"$RC_RELEASE\"}" \ + https://releases.rocket.chat/update + + # Makes build fail if the release isn't there + curl --fail https://releases.rocket.chat/$RC_VERSION/info + + if [[ $GIT_TAG ]]; then + curl -X POST \ + https://connect.redhat.com/api/v2/projects/$REDHAT_REGISTRY_PID/build \ + -H "Authorization: Bearer $REDHAT_REGISTRY_KEY" \ + -H 'Cache-Control: no-cache' \ + -H 'Content-Type: application/json' \ + -d '{"tag":"'$GIT_TAG'"}' fi - else - GIT_TAG="" - GIT_BRANCH="${GITHUB_REF#*heads/}" - ARTIFACT_NAME="$(npm run version --silent).$GITHUB_SHA" - RC_VERSION="$(npm run version --silent)" - SNAP_CHANNEL=edge - RC_RELEASE=develop - fi; - ROCKET_DEPLOY_DIR="/tmp/deploy" - FILENAME="$ROCKET_DEPLOY_DIR/rocket.chat-$ARTIFACT_NAME.tgz"; - - aws s3 cp s3://rocketchat/sign.key.gpg .github/sign.key.gpg - - mkdir -p $ROCKET_DEPLOY_DIR - - cp .github/sign.key.gpg /tmp - gpg --yes --batch --passphrase=$GPG_PASSWORD /tmp/sign.key.gpg - gpg --allow-secret-key-import --import /tmp/sign.key - rm /tmp/sign.key - - ln -s /tmp/build/Rocket.Chat.tar.gz "$FILENAME" - gpg --armor --detach-sign "$FILENAME" - - aws s3 cp $ROCKET_DEPLOY_DIR/ s3://download.rocket.chat/build/ --recursive - - curl -H "Content-Type: application/json" -H "X-Update-Token: $UPDATE_TOKEN" -d \ - "{\"nodeVersion\": \"14.18.2\", \"compatibleMongoVersions\": [\"3.6\", \"4.0\", \"4.2\", \"4.4\", \"5.0\"], \"commit\": \"$GITHUB_SHA\", \"tag\": \"$RC_VERSION\", \"branch\": \"$GIT_BRANCH\", \"artifactName\": \"$ARTIFACT_NAME\", \"releaseType\": \"$RC_RELEASE\"}" \ - https://releases.rocket.chat/update - - # Makes build fail if the release isn't there - curl --fail https://releases.rocket.chat/$RC_VERSION/info - - if [[ $GIT_TAG ]]; then - curl -X POST \ - https://connect.redhat.com/api/v2/projects/$REDHAT_REGISTRY_PID/build \ - -H "Authorization: Bearer $REDHAT_REGISTRY_KEY" \ - -H 'Cache-Control: no-cache' \ - -H 'Content-Type: application/json' \ - -d '{"tag":"'$GIT_TAG'"}' - fi image-build: runs-on: ubuntu-latest @@ -493,103 +617,170 @@ jobs: strategy: matrix: # this is current a mix of variants and different images - release: ["official", "preview", "alpine"] + release: ['official', 'preview', 'alpine'] env: - IMAGE_NAME: "rocketchat/rocket.chat" + IMAGE_NAME: 'rocketchat/rocket.chat' steps: - - uses: actions/checkout@v2 - - - name: Login to DockerHub - uses: docker/login-action@v1 - with: - username: ${{ secrets.DOCKER_USER }} - password: ${{ secrets.DOCKER_PASS }} - - - name: Restore build - uses: actions/download-artifact@v2 - with: - name: build - path: /tmp/build - - - name: Unpack build and prepare Docker files - run: | - cd /tmp/build - tar xzf Rocket.Chat.tar.gz - rm Rocket.Chat.tar.gz - - DOCKER_PATH="${GITHUB_WORKSPACE}/.docker" - if [[ '${{ matrix.release }}' = 'preview' ]]; then - DOCKER_PATH="${DOCKER_PATH}-mongo" - fi; + - uses: actions/checkout@v3 + + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASS }} + + - name: Restore build + uses: actions/download-artifact@v2 + with: + name: build + path: /tmp/build + + - name: Unpack build and prepare Docker files + run: | + cd /tmp/build + tar xzf Rocket.Chat.tar.gz + rm Rocket.Chat.tar.gz + + DOCKER_PATH="${GITHUB_WORKSPACE}/.docker" + if [[ '${{ matrix.release }}' = 'preview' ]]; then + DOCKER_PATH="${DOCKER_PATH}-mongo" + fi; + + DOCKERFILE_PATH="${DOCKER_PATH}/Dockerfile" + if [[ '${{ matrix.release }}' = 'alpine' ]]; then + DOCKERFILE_PATH="${DOCKERFILE_PATH}.${{ matrix.release }}" + fi; + + echo "Copy Dockerfile for release: ${{ matrix.release }}" + cp $DOCKERFILE_PATH ./Dockerfile + if [ -e ${DOCKER_PATH}/entrypoint.sh ]; then + cp ${DOCKER_PATH}/entrypoint.sh . + fi; + + - name: Build Docker image for tag + if: github.event_name == 'release' + run: | + cd /tmp/build + + DOCKER_TAG=$GITHUB_REF_NAME + + if [[ '${{ matrix.release }}' = 'preview' ]]; then + IMAGE_NAME="${IMAGE_NAME}.preview" + fi; + + # append the variant name to docker tag + if [[ '${{ matrix.release }}' = 'alpine' ]]; then + DOCKER_TAG="${DOCKER_TAG}-${{ matrix.release }}" + fi; + + if echo "$GITHUB_REF_NAME" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$' ; then + RELEASE="latest" + elif echo "$GITHUB_REF_NAME" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$' ; then + RELEASE="release-candidate" + fi - DOCKERFILE_PATH="${DOCKER_PATH}/Dockerfile" - if [[ '${{ matrix.release }}' = 'alpine' ]]; then - DOCKERFILE_PATH="${DOCKERFILE_PATH}.${{ matrix.release }}" - fi; + if [[ '${{ matrix.release }}' = 'alpine' ]]; then + RELEASE="${RELEASE}-${{ matrix.release }}" + fi; + + echo "IMAGE_NAME: $IMAGE_NAME" + echo "DOCKER_TAG: $DOCKER_TAG" + echo "RELEASE: $RELEASE" + + # build and push the specific tag version + docker build -t $IMAGE_NAME:$DOCKER_TAG . + docker push $IMAGE_NAME:$DOCKER_TAG + + if [[ $RELEASE == 'latest' ]]; then + CURRENT_LATEST="$( + git -c 'versionsort.suffix=-' ls-remote -t --exit-code --refs --sort=-v:refname "https://github.com/$GITHUB_REPOSITORY" '*' | + sed -En '1!q;s/^[[:xdigit:]]+[[:space:]]+refs\/tags\/(.+)/\1/gp' + )" + echo "CURRENT_LATEST: $CURRENT_LATEST" + if [[ $CURRENT_LATEST == $GITHUB_REF_NAME ]]; then + docker tag $IMAGE_NAME:$DOCKER_TAG $IMAGE_NAME:$RELEASE + docker push $IMAGE_NAME:$RELEASE + fi + else + docker tag $IMAGE_NAME:$DOCKER_TAG $IMAGE_NAME:$RELEASE + docker push $IMAGE_NAME:$RELEASE + fi - echo "Copy Dockerfile for release: ${{ matrix.release }}" - cp $DOCKERFILE_PATH ./Dockerfile - if [ -e ${DOCKER_PATH}/entrypoint.sh ]; then - cp ${DOCKER_PATH}/entrypoint.sh . - fi; + - name: Build Docker image for develop + if: github.ref == 'refs/heads/develop' + run: | + cd /tmp/build - - name: Build Docker image for tag - if: github.event_name == 'release' - run: | - cd /tmp/build + DOCKER_TAG=develop - DOCKER_TAG=$GITHUB_REF_NAME + if [[ '${{ matrix.release }}' = 'preview' ]]; then + IMAGE_NAME="${IMAGE_NAME}.preview" + fi; - if [[ '${{ matrix.release }}' = 'preview' ]]; then - IMAGE_NAME="${IMAGE_NAME}.preview" - fi; + if [[ '${{ matrix.release }}' = 'alpine' ]]; then + DOCKER_TAG="${DOCKER_TAG}-${{ matrix.release }}" + fi; - # append the variant name to docker tag - if [[ '${{ matrix.release }}' = 'alpine' ]]; then - DOCKER_TAG="${DOCKER_TAG}-${{ matrix.release }}" - fi; + docker build -t $IMAGE_NAME:$DOCKER_TAG . + docker push $IMAGE_NAME:$DOCKER_TAG - if echo "$GITHUB_REF_NAME" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$' ; then - RELEASE="latest" - elif echo "$GITHUB_REF_NAME" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$' ; then - RELEASE="release-candidate" - fi + services-image-build: + runs-on: ubuntu-20.04 + needs: deploy - if [[ '${{ matrix.release }}' = 'alpine' ]]; then - RELEASE="${RELEASE}-${{ matrix.release }}" - fi; + strategy: + matrix: + service: ['account', 'authorization', 'ddp-streamer', 'presence', 'stream-hub'] - echo "IMAGE_NAME: $IMAGE_NAME" - echo "DOCKER_TAG: $DOCKER_TAG" - echo "RELEASE: $RELEASE" + steps: + - uses: actions/checkout@v3 + + - name: Use Node.js 14.18.3 + uses: actions/setup-node@v3 + with: + node-version: '14.18.3' + + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASS }} + + - name: Build Docker images + run: | + # defines image tag + if [[ $GITHUB_REF == refs/tags/* ]]; then + IMAGE_TAG="${GITHUB_REF#refs/tags/}" + else + IMAGE_TAG="${GITHUB_REF#refs/heads/}" + fi - # build and push the specific tag version - docker build -t $IMAGE_NAME:$DOCKER_TAG . - docker push $IMAGE_NAME:$DOCKER_TAG + # first install repo dependencies + npm i - # build and push the broader alias tag - docker tag $IMAGE_NAME:$DOCKER_TAG $IMAGE_NAME:$RELEASE - docker push $IMAGE_NAME:$RELEASE + # then micro services dependencies + cd ./ee/server/services + npm i + npm run build - - name: Build Docker image for develop - if: github.ref == 'refs/heads/develop' - run: | - cd /tmp/build + echo "Building Docker image for service: ${{ matrix.service }}:${IMAGE_TAG}" - DOCKER_TAG=develop + docker build --build-arg SERVICE=${{ matrix.service }} -t rocketchat/${{ matrix.service }}-service:${IMAGE_TAG} . - if [[ '${{ matrix.release }}' = 'preview' ]]; then - IMAGE_NAME="${IMAGE_NAME}.preview" - fi; + docker push rocketchat/${{ matrix.service }}-service:${IMAGE_TAG} - if [[ '${{ matrix.release }}' = 'alpine' ]]; then - DOCKER_TAG="${DOCKER_TAG}-${{ matrix.release }}" - fi; + if [[ $GITHUB_REF == refs/tags/* ]]; then + if echo "$IMAGE_TAG" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$' ; then + RELEASE="latest" + elif echo "$IMAGE_TAG" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$' ; then + RELEASE="release-candidate" + fi - docker build -t $IMAGE_NAME:$DOCKER_TAG . - docker push $IMAGE_NAME:$DOCKER_TAG + docker tag rocketchat/${{ matrix.service }}-service:${IMAGE_TAG} rocketchat/${{ matrix.service }}-service:${RELEASE} + docker push rocketchat/${{ matrix.service }}-service:${RELEASE} + fi acc-ecr-push: runs-on: ubuntu-latest @@ -609,7 +800,6 @@ jobs: git_hash=$(git rev-parse --short "$GITHUB_SHA") rocket_version=$(node -pe "require('./package.json').version") echo "PR_TAG=v$rocket_version.$git_hash" >> $GITHUB_ENV - - name: Restore build uses: actions/download-artifact@v1 with: @@ -622,7 +812,6 @@ jobs: tar xzf Rocket.Chat.tar.gz rm Rocket.Chat.tar.gz cp "${GITHUB_WORKSPACE}/.docker/Dockerfile" . - ## Push to ACC:rocketchat - name: Configure AWS credentials in ACC uses: aws-actions/configure-aws-credentials@v1 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index da9570060ed4..3b8c2696cd1c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,4 +1,4 @@ -name: "Code scanning - action" +name: 'Code scanning - action' on: push: @@ -8,45 +8,44 @@ on: jobs: CodeQL-Build: - # CodeQL runs on ubuntu-latest and windows-latest runs-on: ubuntu-latest steps: - - name: Checkout repository - uses: actions/checkout@v2 - with: - # We must fetch at least the immediate parents so that if this is - # a pull request then we can checkout the head. - fetch-depth: 2 - - # If this run was triggered by a pull request event, then checkout - # the head of the pull request instead of the merge commit. - - run: git checkout HEAD^2 - if: ${{ github.event_name == 'pull_request' }} - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - # Override language selection by uncommenting this and choosing your languages - with: - languages: javascript - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 - - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + - name: Checkout repository + uses: actions/checkout@v3 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 + + # If this run was triggered by a pull request event, then checkout + # the head of the pull request instead of the merge commit. + - run: git checkout HEAD^2 + if: ${{ github.event_name == 'pull_request' }} + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + # Override language selection by uncommenting this and choosing your languages + with: + languages: javascript + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.gitignore b/.gitignore index 8d5cc726ac80..1443aac7da70 100644 --- a/.gitignore +++ b/.gitignore @@ -80,5 +80,6 @@ tests/end-to-end/temporary_staged_test /private/livechat /storybook-static /tests/cypress/screenshots +/tests/cypress/videos coverage .nyc_output diff --git a/.houston/metadata.js b/.houston/metadata.js index 44ff8721f408..9836f5709461 100644 --- a/.houston/metadata.js +++ b/.houston/metadata.js @@ -7,7 +7,7 @@ const getMongoVersion = async function({ version, git }) { return []; } - return mongoMatch[1].replace(/"/g, '').replace(/ /g, '').split(','); + return mongoMatch[1].replace(/["' ]/g, '').split(','); } catch (e) { console.error(e); } diff --git a/.meteor/packages b/.meteor/packages index 0be8da158f2a..a19b1d6cfec3 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -67,7 +67,7 @@ littledata:synced-cron edgee:slingshot jalik:ufs-local@1.0.2 -accounts-base@2.2.0 +accounts-base@2.2.1 accounts-oauth@1.4.0 autoupdate@1.8.0 babel-compiler@7.8.0 @@ -76,7 +76,7 @@ htmljs less matb33:collection-hooks meteorhacks:inject-initial -oauth@2.1.0 +oauth@2.1.1 oauth2@1.3.1 routepolicy@1.1.1 sha@1.0.9 @@ -88,3 +88,4 @@ rocketchat:i18n rocketchat:postcss dandv:caret-position facts-base@1.0.1 +url diff --git a/.meteor/release b/.meteor/release index c2ff29ae85a8..0150b11e9f5b 100644 --- a/.meteor/release +++ b/.meteor/release @@ -1 +1 @@ -METEOR@2.5.3 +METEOR@2.5.6 diff --git a/.meteor/versions b/.meteor/versions index f0bba23468b2..7ba89bd6e6f9 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -1,4 +1,4 @@ -accounts-base@2.2.0 +accounts-base@2.2.1 accounts-facebook@1.3.3 accounts-github@1.5.0 accounts-google@1.4.0 diff --git a/.mocharc.js b/.mocharc.js index 5f18ee8009b0..4a93bfe2351c 100644 --- a/.mocharc.js +++ b/.mocharc.js @@ -23,5 +23,5 @@ Object.assign( module.exports = { ...base, // see https://github.com/mochajs/mocha/issues/3916 exit: true, - spec: ['app/**/*.spec.ts', 'app/**/*.tests.js', 'app/**/*.tests.ts', 'server/**/*.tests.ts'], + spec: ['app/**/*.spec.ts', 'app/**/*.tests.js', 'app/**/*.tests.ts', 'server/**/*.tests.ts', 'ee/**/*.tests.ts', 'lib/**/*.spec.ts'], }; diff --git a/.scripts/start.js b/.scripts/start.js index 3203ba1ec5ec..b5001b8b7102 100644 --- a/.scripts/start.js +++ b/.scripts/start.js @@ -2,12 +2,10 @@ const path = require('path'); const fs = require('fs'); -const extend = require('util')._extend; const { spawn } = require('child_process'); const net = require('net'); const processes = []; -let exitCode; const baseDir = path.resolve(__dirname, '..'); const srcDir = path.resolve(baseDir); @@ -30,7 +28,7 @@ const waitPortRelease = (port, count = 0) => if (count > 60) { return reject(); } - console.log('Port', port, 'not release, waiting 1s...'); + console.log('Port', port, 'not released, waiting 1s...'); setTimeout(() => { waitPortRelease(port, ++count) .then(resolve) @@ -43,22 +41,39 @@ const appOptions = { env: { PORT: 3000, ROOT_URL: 'http://localhost:3000', - // MONGO_URL: 'mongodb://localhost:27017/test', - // MONGO_OPLOG_URL: 'mongodb://localhost:27017/local', }, }; -function startProcess(opts, callback) { - const proc = spawn(opts.command, opts.params, opts.options); +let killingAllProcess = false; +function killAllProcesses(mainExitCode) { + if (killingAllProcess) { + return; + } + killingAllProcess = true; - if (opts.waitForMessage) { - proc.stdout.on('data', function waitForMessage(data) { - if (data.toString().match(opts.waitForMessage)) { - if (callback) { - callback(); - } - } + processes.forEach((p) => { + console.log('Killing process', p.pid); + p.kill(); + }); + + waitPortRelease(appOptions.env.PORT) + .then(() => { + console.log(`Port ${appOptions.env.PORT} was released, exiting with code ${mainExitCode}`); + process.exit(mainExitCode); + }) + .catch((error) => { + console.error(`Error waiting port ${appOptions.env.PORT} to be released, exiting with code ${mainExitCode}`); + console.error(error); + process.exit(mainExitCode); }); +} + +function startProcess(opts) { + const proc = spawn(opts.command, opts.params, opts.options); + processes.push(proc); + + if (opts.onData) { + proc.stdout.on('data', opts.onData); } if (!opts.silent) { @@ -73,74 +88,103 @@ function startProcess(opts, callback) { } proc.on('exit', function (code, signal) { + processes.splice(processes.indexOf(proc), 1); + if (code != null) { - exitCode = code; console.log(opts.name, `exited with code ${code}`); } else { console.log(opts.name, `exited with signal ${signal}`); } - processes.splice(processes.indexOf(proc), 1); - - processes.forEach((p) => { - console.log('Killing process', p.pid); - p.kill(); - }); - - if (processes.length === 0) { - waitPortRelease(appOptions.env.PORT) - .then(() => { - console.log(`Port ${appOptions.env.PORT} was released, exiting with code ${exitCode}`); - process.exit(exitCode); - }) - .catch((error) => { - console.error(`Error waiting port ${appOptions.env.PORT} to be released, exiting with code ${exitCode}`); - console.error(error); - process.exit(exitCode); - }); - } + killAllProcesses(code); }); - processes.push(proc); } -function startApp(callback) { - startProcess( - { +function startRocketChat() { + return new Promise((resolve) => { + const waitServerRunning = (message) => { + if (message.toString().match('SERVER RUNNING')) { + return resolve(); + } + }; + + startProcess({ name: 'Meteor App', command: 'node', params: ['/tmp/build-test/bundle/main.js'], - // command: 'node', - // params: ['.meteor/local/build/main.js'], - waitForMessage: appOptions.waitForMessage, + onData: waitServerRunning, options: { cwd: srcDir, - env: extend(appOptions.env, process.env), + env: { + ...appOptions.env, + ...process.env, + }, }, - }, - callback, - ); + }); + }); +} + +async function startMicroservices() { + const startService = (name) => { + return new Promise((resolve) => { + const waitStart = (message) => { + if (message.toString().match('NetworkBroker started successfully')) { + return resolve(); + } + }; + startProcess({ + name: `${name} service`, + command: 'node', + params: ['service.js'], + onData: waitStart, + options: { + cwd: path.resolve(srcDir, 'ee', 'server', 'services', 'dist', 'ee', 'server', 'services', name), + env: { + ...appOptions.env, + ...process.env, + PORT: 4000, + }, + }, + }); + }); + }; + + await Promise.all([ + startService('account'), + startService('authorization'), + startService('ddp-streamer'), + startService('presence'), + startService('stream-hub'), + ]); } -function startChimp() { +function startTests(options = []) { + const testOption = options.find((i) => i.startsWith('--test=')); + const testParam = testOption ? testOption.replace('--test=', '') : 'test'; + + console.log(`Running test "npm run ${testParam}"`); + startProcess({ - name: 'Chimp', + name: 'Tests', command: 'npm', - params: ['test'], - // command: 'exit', - // params: ['2'], + params: ['run', testParam], options: { - env: Object.assign({}, process.env, { + env: { + ...process.env, NODE_PATH: `${process.env.NODE_PATH + path.delimiter + srcDir + path.delimiter + srcDir}/node_modules`, - }), + }, }, }); } -function chimpNoMirror() { - appOptions.waitForMessage = 'SERVER RUNNING'; - startApp(function () { - startChimp(); - }); -} +(async () => { + const [, , ...options] = process.argv; + + await startRocketChat(); + + if (options.includes('--enterprise')) { + await startMicroservices(); + } -chimpNoMirror(); + startTests(options); +})(); diff --git a/.snapcraft/resources/prepareRocketChat b/.snapcraft/resources/prepareRocketChat index 43bf6e18f326..5dc6601389fa 100755 --- a/.snapcraft/resources/prepareRocketChat +++ b/.snapcraft/resources/prepareRocketChat @@ -1,6 +1,6 @@ #!/bin/bash -curl -SLf "https://releases.rocket.chat/4.4.2/download/" -o rocket.chat.tgz +curl -SLf "https://releases.rocket.chat/4.6.3/download/" -o rocket.chat.tgz tar xf rocket.chat.tgz --strip 1 diff --git a/.snapcraft/resources/preparenode b/.snapcraft/resources/preparenode index 186fc26dd695..d6619dd9bf4d 100755 --- a/.snapcraft/resources/preparenode +++ b/.snapcraft/resources/preparenode @@ -1,6 +1,6 @@ #!/bin/bash -node_version="v14.18.2" +node_version="v14.18.3" unamem="$(uname -m)" if [[ $unamem == *aarch64* ]]; then diff --git a/.snapcraft/snap/snapcraft.yaml b/.snapcraft/snap/snapcraft.yaml index 4a4ce8aa2ff7..b5de4d45eb70 100644 --- a/.snapcraft/snap/snapcraft.yaml +++ b/.snapcraft/snap/snapcraft.yaml @@ -7,7 +7,7 @@ # 5. `snapcraft snap` name: rocketchat-server -version: 4.4.2 +version: 4.6.3 summary: Rocket.Chat server description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/ confinement: strict diff --git a/.storybook/.prettierrc b/.storybook/.prettierrc deleted file mode 120000 index 4031483e531f..000000000000 --- a/.storybook/.prettierrc +++ /dev/null @@ -1 +0,0 @@ -../client/.prettierrc \ No newline at end of file diff --git a/.storybook/babel.config.js b/.storybook/babel.config.js index f028aa2d2940..666946b95d78 100644 --- a/.storybook/babel.config.js +++ b/.storybook/babel.config.js @@ -11,6 +11,7 @@ module.exports = { ], '@babel/preset-react', '@babel/preset-flow', + '@babel/preset-typescript', ], plugins: [ '@babel/plugin-proposal-class-properties', diff --git a/.storybook/decorators.tsx b/.storybook/decorators.tsx index 9314684c128f..3da8357a1230 100644 --- a/.storybook/decorators.tsx +++ b/.storybook/decorators.tsx @@ -1,10 +1,13 @@ +import { DecoratorFunction } from '@storybook/addons'; import React, { ReactElement } from 'react'; -import { MeteorProviderMock } from './mocks/providers'; -import QueryClientProviderMock from './mocks/providers/QueryClientProviderMock'; -import ServerProviderMock from './mocks/providers/ServerProviderMock'; +import ModalContextMock from '../client/stories/contexts/ModalContextMock'; +import QueryClientProviderMock from '../client/stories/contexts/QueryClientProviderMock'; +import RouterContextMock from '../client/stories/contexts/RouterContextMock'; +import ServerContextMock from '../client/stories/contexts/ServerContextMock'; +import TranslationContextMock from '../client/stories/contexts/TranslationContextMock'; -export const rocketChatDecorator = (storyFn: () => ReactElement): ReactElement => { +export const rocketChatDecorator: DecoratorFunction> = (fn, { parameters }) => { const linkElement = document.getElementById('theme-styles') || document.createElement('link'); if (linkElement.id !== 'theme-styles') { require('../app/theme/client/main.css'); @@ -22,42 +25,21 @@ export const rocketChatDecorator = (storyFn: () => ReactElement): ReactElement = return ( - - - -
-
{storyFn()}
- - + + + + + +
+
{fn()}
+ + + + ); }; - -export const fullHeightDecorator = (storyFn: () => ReactElement): ReactElement => ( -
- {storyFn()} -
-); - -export const centeredDecorator = (storyFn: () => ReactElement): ReactElement => ( -
- {storyFn()} -
-); diff --git a/.storybook/hooks/index.ts b/.storybook/hooks/index.ts deleted file mode 100644 index ca0d1db71f7f..000000000000 --- a/.storybook/hooks/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './useAutoToggle'; diff --git a/.storybook/hooks/useAutoToggle.ts b/.storybook/hooks/useAutoToggle.ts deleted file mode 100644 index 542ae89d17a1..000000000000 --- a/.storybook/hooks/useAutoToggle.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { useEffect, useState } from 'react'; - -export const useAutoToggle = (initialValue = false, ms = 1000): boolean => { - const [value, setValue] = useState(initialValue); - - useEffect(() => { - const timer = setInterval(() => setValue((value) => !value), ms); - - return (): void => { - clearInterval(timer); - }; - }, [ms]); - - return value; -}; diff --git a/.storybook/main.js b/.storybook/main.js index f5d134f36d2e..afb264d66851 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -3,15 +3,8 @@ const { resolve, relative, join } = require('path'); const webpack = require('webpack'); module.exports = { - stories: [ - '../app/**/*.stories.{js,tsx}', - '../client/**/*.stories.{js,tsx}', - ...(process.env.EE === 'true' ? ['../ee/**/*.stories.{js,tsx}'] : []), - ], - addons: ['@storybook/addon-essentials', '@storybook/addon-postcss'], - typescript: { - reactDocgen: 'none', - }, + stories: ['../app/**/*.stories.{js,tsx}', '../client/**/*.stories.{js,tsx}', '../ee/**/*.stories.{js,tsx}'], + addons: ['@storybook/addon-essentials', '@storybook/addon-interactions', '@storybook/addon-postcss'], webpackFinal: async (config) => { const cssRule = config.module.rules.find(({ test }) => test.test('index.css')); @@ -48,18 +41,6 @@ module.exports = { use: '@settlin/spacebars-loader', }); - config.module.rules.push({ - test: /\.(ts|tsx)$/, - use: [ - { - loader: 'ts-loader', - options: { - configFile: join(__dirname, '../tsconfig.webpack.json'), - }, - }, - ], - }); - config.plugins.push( new webpack.NormalModuleReplacementPlugin(/^meteor/, require.resolve('./mocks/meteor.js')), new webpack.NormalModuleReplacementPlugin(/(app)\/*.*\/(server)\/*/, require.resolve('./mocks/empty.ts')), diff --git a/.storybook/mocks/meteor.js b/.storybook/mocks/meteor.js index ef22c95f67a7..8ec804ef826f 100644 --- a/.storybook/mocks/meteor.js +++ b/.storybook/mocks/meteor.js @@ -74,6 +74,7 @@ export const Template = Object.assign( export const Blaze = { Template, registerHelper: () => {}, + renderWithData: () => {}, }; window.Blaze = Blaze; diff --git a/.storybook/mocks/providers/ServerProviderMock.tsx b/.storybook/mocks/providers/ServerProviderMock.tsx deleted file mode 100644 index 96088134407a..000000000000 --- a/.storybook/mocks/providers/ServerProviderMock.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { action } from '@storybook/addon-actions'; -import React, { ContextType, FC } from 'react'; - -import { ServerContext, ServerMethodName, ServerMethodParameters, ServerMethodReturn } from '../../../client/contexts/ServerContext'; -import { Serialized } from '../../../definition/Serialized'; -import { MatchPathPattern, Method, OperationParams, OperationResult, PathFor } from '../../../definition/rest'; - -const logAction = action('ServerProvider'); - -const randomDelay = (): Promise => new Promise((resolve) => setTimeout(resolve, Math.random() * 1000)); - -const absoluteUrl = (path: string): string => new URL(path, '/').toString(); - -const callMethod = ( - methodName: MethodName, - ...args: ServerMethodParameters -): Promise> => - Promise.resolve(logAction('callMethod', methodName, ...args)) - .then(randomDelay) - .then(() => undefined as any); - -const callEndpoint = >( - method: TMethod, - path: TPath, - params: Serialized>>, -): Promise>>> => - Promise.resolve(logAction('callEndpoint', method, path, params)) - .then(randomDelay) - .then(() => undefined as any); - -const uploadToEndpoint = (endpoint: string, params: any, formData: any): Promise => - Promise.resolve(logAction('uploadToEndpoint', endpoint, params, formData)).then(randomDelay); - -const getStream = (streamName: string, options: {} = {}): ((eventName: string, callback: (data: T) => void) => () => void) => { - logAction('getStream', streamName, options); - - return (eventName, callback): (() => void) => { - const subId = Math.random().toString(16).slice(2); - logAction('getStream.subscribe', streamName, eventName, subId); - - randomDelay().then(() => callback(undefined as any)); - - return (): void => { - logAction('getStream.unsubscribe', streamName, eventName, subId); - }; - }; -}; - -const ServerProviderMock: FC>> = ({ children, ...overrides }) => ( - -); - -export default ServerProviderMock; diff --git a/.storybook/mocks/providers/index.tsx b/.storybook/mocks/providers/index.tsx deleted file mode 100644 index 48cbbbaa438c..000000000000 --- a/.storybook/mocks/providers/index.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import i18next from 'i18next'; -import React, { PropsWithChildren, ReactElement } from 'react'; - -import { TranslationContext, TranslationContextValue } from '../../../client/contexts/TranslationContext'; - -let contextValue: TranslationContextValue; - -const getContextValue = (): TranslationContextValue => { - if (contextValue) { - return contextValue; - } - - i18next.init({ - fallbackLng: 'en', - defaultNS: 'project', - resources: { - en: { - project: require('../../../packages/rocketchat-i18n/i18n/en.i18n.json'), - }, - }, - interpolation: { - prefix: '__', - suffix: '__', - }, - initImmediate: false, - }); - - const translate = (key: string, ...replaces: unknown[]): string => { - if (typeof replaces[0] === 'object' && replaces[0] !== null) { - const [options] = replaces; - return i18next.t(key, options); - } - - if (replaces.length === 0) { - return i18next.t(key); - } - - return i18next.t(key, { - postProcess: 'sprintf', - sprintf: replaces, - }); - }; - - translate.has = (key: string): boolean => !!key && i18next.exists(key); - - contextValue = { - languages: [ - { - name: 'English', - en: 'English', - key: 'en', - }, - ], - language: 'en', - translate, - loadLanguage: async (): Promise => undefined, - }; - - return contextValue; -}; - -function TranslationProviderMock({ children }: PropsWithChildren<{}>): ReactElement { - return ; -} - -// eslint-disable-next-line react/no-multi-comp -export function MeteorProviderMock({ children }: PropsWithChildren<{}>): ReactElement { - return {children}; -} diff --git a/.storybook/preview.ts b/.storybook/preview.ts index ab9bd5a3220d..2b1ed2526995 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -18,6 +18,9 @@ addParameters({ page: DocsPage, }, options: { - storySort: ([, a], [, b]): number => a.kind.localeCompare(b.kind), + storySort: { + method: 'alphabetical', + order: ['Components', '*', 'Enterprise'], + }, }, }); diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index c644d9ba3f99..8b863ff5d4a1 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -3,7 +3,7 @@ ## Our Pledge In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and +contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and @@ -56,7 +56,7 @@ further defined and clarified by project maintainers. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at team@rocket.chat. The project team -will review and investigate all complaints, and will respond in a way that it deems +will review and investigate all complaints and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. diff --git a/FEATURES.md b/FEATURES.md index 381c09a0baf0..e4b11c141184 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -48,7 +48,7 @@ - Reactions - Message editing - Editing is as simple as using your arrow keys for picking the right message to edit - - Setup to keep history of edits or discard the previous text + - Setup to keep the history of edits or discard the previous text - Show or hide edited/deleted status - History - Search @@ -58,10 +58,10 @@ - Add stars and pins to messages - Star messages that are important to you. Only you have access to your stars. - Pin messages that are important to everyone. - - Access your starred/pinned and messages you were mentioned on quickly through side bar buttons -- REST Api + - Access your starred/pinned and messages you were mentioned on quickly through sidebar buttons +- REST API - Roles and Permissions - Public and Private multi-user rooms - One-on-one conversations - - Off-the-record messaging (messages are encrypted and transiently saved on database) -- Slashcommands + - Off-the-record messaging (messages are encrypted and transiently saved on the database) +- Slash commands diff --git a/HISTORY.md b/HISTORY.md index ebef69f92788..979db5e1decc 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,1224 @@ +# 4.6.3 +`2022-04-19 · 1 🐛 · 1 👩‍💻👨‍💻` + +### Engine versions +- Node: `14.18.3` +- NPM: `6.14.15` +- MongoDB: `3.6, 4.0, 4.2, 4.4, 5.0` +- Apps-Engine: `1.31.0` + +### 🐛 Bug fixes + + +- Desktop notification on multi-instance environments ([#25220](https://github.com/RocketChat/Rocket.Chat/pull/25220)) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@sampaiodiego](https://github.com/sampaiodiego) + +# 4.6.2 +`2022-04-14 · 2 🐛 · 2 👩‍💻👨‍💻` + +### Engine versions +- Node: `14.18.3` +- NPM: `6.14.15` +- MongoDB: `3.6, 4.0, 4.2, 4.4, 5.0` +- Apps-Engine: `1.31.0` + +### 🐛 Bug fixes + + +- Database indexes not being created ([#25101](https://github.com/RocketChat/Rocket.Chat/pull/25101)) + +- Deactivating user breaks if user is the only room owner ([#24933](https://github.com/RocketChat/Rocket.Chat/pull/24933) by [@sidmohanty11](https://github.com/sidmohanty11)) + + ## Before + + https://user-images.githubusercontent.com/73601258/160000871-cfc2f2a5-2a59-4d27-8049-7754d003dd48.mp4 + + + + ## After + https://user-images.githubusercontent.com/73601258/159998287-681ab475-ff33-4282-82ff-db751c59a392.mp4 + +### 👩‍💻👨‍💻 Contributors 😍 + +- [@sidmohanty11](https://github.com/sidmohanty11) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@sampaiodiego](https://github.com/sampaiodiego) + +# 4.6.1 +`2022-04-07 · 6 🐛 · 5 👩‍💻👨‍💻` + +### Engine versions +- Node: `14.18.3` +- NPM: `6.14.15` +- MongoDB: `3.6, 4.0, 4.2, 4.4, 5.0` +- Apps-Engine: `1.31.0` + +### 🐛 Bug fixes + + +- FormData uploads not working ([#25069](https://github.com/RocketChat/Rocket.Chat/pull/25069)) + +- Invitation links don't redirect to the registration form ([#25082](https://github.com/RocketChat/Rocket.Chat/pull/25082)) + +- NPS never finishing sending results ([#25067](https://github.com/RocketChat/Rocket.Chat/pull/25067)) + +- Proxy settings being ignored ([#25022](https://github.com/RocketChat/Rocket.Chat/pull/25022)) + + Modify Meteor's `HTTP.call` to add back proxy support + +- Upgrade Tab showing for a split second ([#25050](https://github.com/RocketChat/Rocket.Chat/pull/25050)) + +- UserAutoComplete not rendering UserAvatar correctly ([#25055](https://github.com/RocketChat/Rocket.Chat/pull/25055)) + + ### before + ![Screen Shot 2022-04-04 at 16 50 21](https://user-images.githubusercontent.com/27704687/161620921-800bf66a-806d-4f83-b2e1-073c34215001.png) + + ### after + ![Screen Shot 2022-04-04 at 16 49 00](https://user-images.githubusercontent.com/27704687/161620720-3e27774d-c241-46ca-b764-932a9295d709.png) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@dougfabris](https://github.com/dougfabris) +- [@gabriellsh](https://github.com/gabriellsh) +- [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) +- [@sampaiodiego](https://github.com/sampaiodiego) +- [@yash-rajpal](https://github.com/yash-rajpal) + +# 4.6.0 +`2022-04-01 · 2 🎉 · 7 🚀 · 57 🐛 · 62 🔍 · 34 👩‍💻👨‍💻` + +### Engine versions +- Node: `14.18.3` +- NPM: `6.14.15` +- MongoDB: `3.6, 4.0, 4.2, 4.4, 5.0` +- Apps-Engine: `1.31.0` + +### 🎉 New features + + +- Telemetry Events ([#24781](https://github.com/RocketChat/Rocket.Chat/pull/24781) by [@eduardofcabrera](https://github.com/eduardofcabrera) & [@ostjen](https://github.com/ostjen)) + +- Upgrade Tab ([#24835](https://github.com/RocketChat/Rocket.Chat/pull/24835)) + + ![image](https://user-images.githubusercontent.com/27704687/160172260-c656282e-a487-4092-948d-d11c9bacb598.png) + +### 🚀 Improvements + + +- **ENTERPRISE:** Don't start presence monitor when running micro services ([#24739](https://github.com/RocketChat/Rocket.Chat/pull/24739)) + +- Adding new statistics related to voip and omnichannel ([#24887](https://github.com/RocketChat/Rocket.Chat/pull/24887)) + + - Total of Canned response messages sent + - Total of tags used + - Last-Chatted Agent Preferred (enabled/disabled) + - Assign new conversations to the contact manager (enabled/disabled) + - How to handle Visitor Abandonment setting + - Amount of chats placed on hold + - VoIP Enabled + - Amount of VoIP Calls + - Amount of VoIP Extensions connected + - Amount of Calls placed on hold (1x per call) + - Fixed Session Aggregation type definitions + +- New omnichannel statistics and async statistics processing. ([#24749](https://github.com/RocketChat/Rocket.Chat/pull/24749)) + + https://app.clickup.com/t/1z4zg4e + +- Standarize queue behavior for managers and agents when subscribing ([#24837](https://github.com/RocketChat/Rocket.Chat/pull/24837)) + +- Updated links in readme ([#24028](https://github.com/RocketChat/Rocket.Chat/pull/24028) by [@aswinidev](https://github.com/aswinidev)) + +- UX - VoIP Call Component ([#24748](https://github.com/RocketChat/Rocket.Chat/pull/24748)) + +- Voip Extensions disabled state ([#24750](https://github.com/RocketChat/Rocket.Chat/pull/24750)) + +### 🐛 Bug fixes + + +- "livechat/webrtc.call" endpoint not working ([#24804](https://github.com/RocketChat/Rocket.Chat/pull/24804)) + +- "Match error" when converting a team to a channel ([#24629](https://github.com/RocketChat/Rocket.Chat/pull/24629)) + + - Fix "Match error" when trying to convert a channel to a team; + +- **ENTERPRISE:** Auto reload feature of ddp-streamer micro service ([#24793](https://github.com/RocketChat/Rocket.Chat/pull/24793)) + +- **ENTERPRISE:** DDP streamer not sending data to all clients ([#24738](https://github.com/RocketChat/Rocket.Chat/pull/24738)) + +- **ENTERPRISE:** Notifications not being sent by ddp-streamer ([#24831](https://github.com/RocketChat/Rocket.Chat/pull/24831)) + +- **ENTERPRISE:** Presence micro service logic ([#24724](https://github.com/RocketChat/Rocket.Chat/pull/24724)) + +- **VOIP:** SidebarFooter component ([#24838](https://github.com/RocketChat/Rocket.Chat/pull/24838)) + + - Improve the CallProvider code; + - Adjust the text case of the VoIP component on the FooterSidebar; + - Fix the bad behavior with the changes in queue's name. + +- `PaginatedSelectFiltered` not handling changes ([#24732](https://github.com/RocketChat/Rocket.Chat/pull/24732)) + +- API Error preventing adding an email to users without one (like bot/app users) ([#24709](https://github.com/RocketChat/Rocket.Chat/pull/24709)) + +- Apple login script being loaded even when Apple Login is disabled. ([#24760](https://github.com/RocketChat/Rocket.Chat/pull/24760)) + +- Apple OAuth ([#24879](https://github.com/RocketChat/Rocket.Chat/pull/24879)) + +- auto-join team channels not honoring user preferences ([#24779](https://github.com/RocketChat/Rocket.Chat/pull/24779) by [@ostjen](https://github.com/ostjen)) + +- Broken build caused by PRs modifying same file differently ([#24863](https://github.com/RocketChat/Rocket.Chat/pull/24863)) + +- Broken multiple OAuth integrations ([#24705](https://github.com/RocketChat/Rocket.Chat/pull/24705)) + +- Components for user search ([#24677](https://github.com/RocketChat/Rocket.Chat/pull/24677)) + +- Critical: Incorrect visitor getting assigned to a chat from apps ([#24805](https://github.com/RocketChat/Rocket.Chat/pull/24805)) + +- Custom script not being fired ([#24901](https://github.com/RocketChat/Rocket.Chat/pull/24901)) + +- Date Message Export Filter Fix ([#24542](https://github.com/RocketChat/Rocket.Chat/pull/24542) by [@eduardofcabrera](https://github.com/eduardofcabrera)) + + Fix message export filter to get all messages between "from date" and "to date", including "to date". + +- DDP Rate Limiter Translation key ([#24898](https://github.com/RocketChat/Rocket.Chat/pull/24898)) + + Before: + image + + + Now: + image + +- DDP streamer errors ([#24710](https://github.com/RocketChat/Rocket.Chat/pull/24710)) + +- Disable voip button when call is in progress ([#24864](https://github.com/RocketChat/Rocket.Chat/pull/24864)) + +- Duplicated 'name' log key ([#24590](https://github.com/RocketChat/Rocket.Chat/pull/24590)) + +- Duplicated "jump to message" button on starred messages ([#24867](https://github.com/RocketChat/Rocket.Chat/pull/24867) by [@Himanshu664](https://github.com/Himanshu664)) + +- External search providers not working ([#24860](https://github.com/RocketChat/Rocket.Chat/pull/24860) by [@tkurz](https://github.com/tkurz)) + +- German translation for Monitore ([#24785](https://github.com/RocketChat/Rocket.Chat/pull/24785) by [@JMoVS](https://github.com/JMoVS)) + +- Handle Other Formats inside Upload Avatar ([#24226](https://github.com/RocketChat/Rocket.Chat/pull/24226) by [@nishant23122000](https://github.com/nishant23122000)) + + After resolving issue #24213 : + + + https://user-images.githubusercontent.com/53515714/150325012-91413025-786e-4ce0-ae75-629f6b05b024.mp4 + +- High CPU usage caused by CallProvider ([#24994](https://github.com/RocketChat/Rocket.Chat/pull/24994)) + + Remove infinity loop inside useVoipClient hook. + + #closes #24970 + +- Ignore customClass on messages ([#24845](https://github.com/RocketChat/Rocket.Chat/pull/24845)) + +- LDAP avatars being rotated according to metadata even if the setting to rotate uploads is off ([#24320](https://github.com/RocketChat/Rocket.Chat/pull/24320)) + + - Use the `FileUpload_RotateImages` setting (**Administration > File Upload > Rotate images on upload**) to control whether avatars should be rotated automatically based on their data (XEIF); + - Display the avatar image preview (orientation) according to the `FileUpload_RotateImages` setting. + +- Missing dependency on useEffect at CallProvider ([#24882](https://github.com/RocketChat/Rocket.Chat/pull/24882)) + +- Missing username on messages imported from Slack ([#24674](https://github.com/RocketChat/Rocket.Chat/pull/24674)) + + - Fix missing sender's username on messages imported from Slack. + +- Nextcloud OAuth for incomplete token URL ([#24476](https://github.com/RocketChat/Rocket.Chat/pull/24476)) + +- no id of room closer in livechat-close message ([#24683](https://github.com/RocketChat/Rocket.Chat/pull/24683)) + +- Opening a new DM from user card ([#24623](https://github.com/RocketChat/Rocket.Chat/pull/24623)) + + A race condition on `useRoomIcon` -- delayed merge of rooms and subscriptions -- was causing a UI crash whenever someone tried to open a DM from the user card component. + +- Prevent call button toggle when user is on call ([#24758](https://github.com/RocketChat/Rocket.Chat/pull/24758)) + +- Prune Message issue ([#24424](https://github.com/RocketChat/Rocket.Chat/pull/24424) by [@nishant23122000](https://github.com/nishant23122000)) + +- Push privacy config to not show username not being respected ([#24606](https://github.com/RocketChat/Rocket.Chat/pull/24606)) + +- Register with Secret URL ([#24921](https://github.com/RocketChat/Rocket.Chat/pull/24921)) + +- Reload roomslist after successful deletion of a room from admin panel. ([#23795](https://github.com/RocketChat/Rocket.Chat/pull/23795) by [@Aman-Maheshwari](https://github.com/Aman-Maheshwari)) + + Removed the logic for calling the `rooms.adminRooms` endPoint from the `RoomsTable` Component and moved it to its parent component `RoomsPage`. + This allows to call the endPoint `rooms.adminRooms` from `EditRoomContextBar` Component which is also has `RoomPage` Component as its parent. + + Also added a succes toast message after the successful deletion of room. + +- Revert AutoComplete ([#24812](https://github.com/RocketChat/Rocket.Chat/pull/24812)) + +- Room archived/unarchived system messages aren't sent when editing room settings ([#24897](https://github.com/RocketChat/Rocket.Chat/pull/24897)) + + - Send the "Room archived" and "Room unarchived" system messages when editing room settings (and not only when rooms are archived/unarchived with the slash-command); + - Fix the "Hide System Messages" option for the "Room archived" and "Room unarchived" system messages; + +- room message not load when is a new message ([#24955](https://github.com/RocketChat/Rocket.Chat/pull/24955)) + + When the room object is searched for the first time, it does not exist on the front object yet (subscription), adding a fallback search for room list will guarantee to search the room details. + + before: + https://user-images.githubusercontent.com/9275105/160223241-d2319f3e-82c5-47d6-867f-695ab2361a17.mp4 + + after: + https://user-images.githubusercontent.com/9275105/160223244-84d0d2a1-3d95-464d-8b8a-e264b0d4d690.mp4 + +- Room's message count not being incremented on import ([#24696](https://github.com/RocketChat/Rocket.Chat/pull/24696)) + + - Fix rooms' message counter not being incremented on message import. + +- SAML Force name to string ([#24930](https://github.com/RocketChat/Rocket.Chat/pull/24930)) + +- Several issues related to custom roles ([#24052](https://github.com/RocketChat/Rocket.Chat/pull/24052)) + + - Throw an error when trying to delete a role (User or Subscription role) that are still being used; + - Fix "Invalid Role" error for custom roles in Role Editing sidebar; + - Fix "Users in Role" screen for custom roles. + +- Show call icon only when user has extension associated ([#24752](https://github.com/RocketChat/Rocket.Chat/pull/24752)) + +- Show only available agents on extension association modal ([#24680](https://github.com/RocketChat/Rocket.Chat/pull/24680)) + +- Show only enabled departments on forward ([#24829](https://github.com/RocketChat/Rocket.Chat/pull/24829)) + +- System messages are sent when adding or removing a group from a team ([#24743](https://github.com/RocketChat/Rocket.Chat/pull/24743)) + + - Do not send system messages when adding or removing a new or existing _group_ from a team. + +- Typo and placeholder on wrap up call modal ([#24737](https://github.com/RocketChat/Rocket.Chat/pull/24737)) + +- Typo in wrap-up term ([#24661](https://github.com/RocketChat/Rocket.Chat/pull/24661)) + +- VoIP button gets disabled whenever user status changes ([#24789](https://github.com/RocketChat/Rocket.Chat/pull/24789)) + +- VoIP Enable/Disable setting on CallContext/CallProvider Notifications ([#24607](https://github.com/RocketChat/Rocket.Chat/pull/24607)) + +- Voip Stream Reinitialization Error ([#24657](https://github.com/RocketChat/Rocket.Chat/pull/24657)) + +- VoipExtensionsPage component call ([#24792](https://github.com/RocketChat/Rocket.Chat/pull/24792)) + +- Wrong business hour behavior ([#24896](https://github.com/RocketChat/Rocket.Chat/pull/24896)) + +- Wrong param usage on queue summary call ([#24799](https://github.com/RocketChat/Rocket.Chat/pull/24799)) + +
+🔍 Minor changes + + +- Bump @rocket.chat/emitter from 0.31.4 to 0.31.9 in /ee/server/services ([#25021](https://github.com/RocketChat/Rocket.Chat/pull/25021) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump @rocket.chat/message-parser from 0.31.4 to 0.31.9 in /ee/server/services ([#25019](https://github.com/RocketChat/Rocket.Chat/pull/25019) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump @rocket.chat/string-helpers from 0.31.4 to 0.31.9 in /ee/server/services ([#25018](https://github.com/RocketChat/Rocket.Chat/pull/25018) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump @rocket.chat/ui-kit from 0.31.4 to 0.31.9 in /ee/server/services ([#25020](https://github.com/RocketChat/Rocket.Chat/pull/25020) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump @types/clipboard from 2.0.1 to 2.0.7 ([#24832](https://github.com/RocketChat/Rocket.Chat/pull/24832) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump @types/mailparser from 3.0.2 to 3.4.0 ([#24833](https://github.com/RocketChat/Rocket.Chat/pull/24833) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump @types/nodemailer from 6.4.2 to 6.4.4 ([#24822](https://github.com/RocketChat/Rocket.Chat/pull/24822) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump @types/ws from 8.2.3 to 8.5.2 in /ee/server/services ([#24666](https://github.com/RocketChat/Rocket.Chat/pull/24666) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump @types/ws from 8.5.2 to 8.5.3 in /ee/server/services ([#24820](https://github.com/RocketChat/Rocket.Chat/pull/24820) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump actions/checkout from 2 to 3 ([#24668](https://github.com/RocketChat/Rocket.Chat/pull/24668) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump actions/setup-node from 2 to 3 ([#24642](https://github.com/RocketChat/Rocket.Chat/pull/24642) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump body-parser from 1.19.0 to 1.19.2 ([#24821](https://github.com/RocketChat/Rocket.Chat/pull/24821) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump is-svg from 4.3.1 to 4.3.2 ([#24801](https://github.com/RocketChat/Rocket.Chat/pull/24801) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump jschardet from 1.6.0 to 3.0.0 ([#23121](https://github.com/RocketChat/Rocket.Chat/pull/23121) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump pino from 7.8.0 to 7.8.1 in /ee/server/services ([#24783](https://github.com/RocketChat/Rocket.Chat/pull/24783) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump pino from 7.8.1 to 7.9.1 in /ee/server/services ([#24869](https://github.com/RocketChat/Rocket.Chat/pull/24869) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump pino-pretty from 7.5.1 to 7.5.2 in /ee/server/services ([#24689](https://github.com/RocketChat/Rocket.Chat/pull/24689) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump pino-pretty from 7.5.2 to 7.5.3 in /ee/server/services ([#24698](https://github.com/RocketChat/Rocket.Chat/pull/24698) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump pino-pretty from 7.5.3 to 7.5.4 in /ee/server/services ([#24870](https://github.com/RocketChat/Rocket.Chat/pull/24870) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump prometheus-gc-stats from 0.6.2 to 0.6.3 ([#24803](https://github.com/RocketChat/Rocket.Chat/pull/24803) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump ts-node from 10.5.0 to 10.6.0 in /ee/server/services ([#24667](https://github.com/RocketChat/Rocket.Chat/pull/24667) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump ts-node from 10.6.0 to 10.7.0 in /ee/server/services ([#24716](https://github.com/RocketChat/Rocket.Chat/pull/24716) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump url-parse from 1.5.7 to 1.5.10 ([#24640](https://github.com/RocketChat/Rocket.Chat/pull/24640) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Chore: Add E2E tests for livechat/room.close ([#24729](https://github.com/RocketChat/Rocket.Chat/pull/24729) by [@Muramatsu2602](https://github.com/Muramatsu2602)) + + * Create a new test suite file under tests/end-to-end/api/livechat + * Create tests for the following endpoint: + + ivechat/room.close + +- Chore: Add E2E tests for livechat/visitor ([#24764](https://github.com/RocketChat/Rocket.Chat/pull/24764) by [@Muramatsu2602](https://github.com/Muramatsu2602)) + + - Create a new test suite file under tests/end-to-end/api/livechat + - Create tests for the following endpoints: + + livechat/visitor (create visitor, update visitor, add custom fields to visitors) + +- Chore: add some missing REST definitions ([#24925](https://github.com/RocketChat/Rocket.Chat/pull/24925)) + + On the [mobile client](https://github.com/RocketChat/Rocket.Chat.ReactNative), we made an effort to collect more `REST API` definitions that are missing on the server side during our migration to TypeScript. Since we're both migrating to TypeScript, we thought it would be a good idea to share those so you guys can benefit from our initiative. + +- Chore: added Server Instances endpoint types ([#24507](https://github.com/RocketChat/Rocket.Chat/pull/24507)) + + Created typing for endpoint definitions on `instances.ts`. + +- Chore: added settings endpoint types ([#24506](https://github.com/RocketChat/Rocket.Chat/pull/24506)) + + Created typing for endpoint definitions on `settings.ts`. + +- Chore: APIClass types ([#24747](https://github.com/RocketChat/Rocket.Chat/pull/24747)) + + This pull request creates a new `restivus` module (.d.ts) for the `api.js` file. + +- Chore: Bump Fuselage packages ([#25015](https://github.com/RocketChat/Rocket.Chat/pull/25015)) + + It uses the last stable version of Fuselage packages. + +- Chore: Convert server functions from javascript to typescript ([#24384](https://github.com/RocketChat/Rocket.Chat/pull/24384)) + + This pull request will be used to rewrite some functions on the Chat Engine to Typescript, in order to increase security and specify variable types on the code. + +- Chore: converted more hooks to typescript ([#24628](https://github.com/RocketChat/Rocket.Chat/pull/24628)) + + Converted some functions on `client/hooks/` from JavaScript to Typescript. + +- Chore: Fix Cypress tests ([#24544](https://github.com/RocketChat/Rocket.Chat/pull/24544)) + +- Chore: Fix grammatical errors in Code of Conduct ([#24759](https://github.com/RocketChat/Rocket.Chat/pull/24759) by [@aadishJ01](https://github.com/aadishJ01)) + +- Chore: fix grammatical errors in Features ([#24771](https://github.com/RocketChat/Rocket.Chat/pull/24771) by [@aadishJ01](https://github.com/aadishJ01)) + +- Chore: Fix MongoDB versions on release notes ([#24877](https://github.com/RocketChat/Rocket.Chat/pull/24877)) + +- Chore: Get Settings Statistics ([#24397](https://github.com/RocketChat/Rocket.Chat/pull/24397)) + +- Chore: Improve logger to allow log of `unknown` values ([#24726](https://github.com/RocketChat/Rocket.Chat/pull/24726)) + +- Chore: Improvements on role syncing (ldap, oauth and saml) ([#23824](https://github.com/RocketChat/Rocket.Chat/pull/23824)) + +- Chore: Micro services fixes and cleanup ([#24753](https://github.com/RocketChat/Rocket.Chat/pull/24753)) + +- Chore: Remove old scripts ([#24911](https://github.com/RocketChat/Rocket.Chat/pull/24911)) + +- Chore: Skip local services changes when shutting down duplicated services ([#24810](https://github.com/RocketChat/Rocket.Chat/pull/24810)) + +- Chore: Storybook mocking and examples improved ([#24969](https://github.com/RocketChat/Rocket.Chat/pull/24969)) + + - Stories from `ee/` included; + - Differentiate root story kinds; + - Mocking of `ServerContext` via Storybook parameters. + +- Chore: Update Livechat ([#24754](https://github.com/RocketChat/Rocket.Chat/pull/24754)) + +- Chore: Update Livechat ([#24990](https://github.com/RocketChat/Rocket.Chat/pull/24990)) + +- Chore(deps-dev): Bump @types/mock-require from 2.0.0 to 2.0.1 ([#24574](https://github.com/RocketChat/Rocket.Chat/pull/24574) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- i18n: Language update from LingoHub 🤖 on 2022-02-28Z ([#24644](https://github.com/RocketChat/Rocket.Chat/pull/24644)) + +- i18n: Language update from LingoHub 🤖 on 2022-03-07Z ([#24717](https://github.com/RocketChat/Rocket.Chat/pull/24717)) + +- i18n: Language update from LingoHub 🤖 on 2022-03-14Z ([#24823](https://github.com/RocketChat/Rocket.Chat/pull/24823)) + +- i18n: Language update from LingoHub 🤖 on 2022-03-21Z ([#24895](https://github.com/RocketChat/Rocket.Chat/pull/24895)) + +- i18n: Language update from LingoHub 🤖 on 2022-03-28Z ([#24971](https://github.com/RocketChat/Rocket.Chat/pull/24971)) + +- Merge master into develop & Set version to 4.6.0-develop ([#24653](https://github.com/RocketChat/Rocket.Chat/pull/24653)) + +- Regression: Add createdOTR index ([#25017](https://github.com/RocketChat/Rocket.Chat/pull/25017)) + +- Regression: Call doesn't stop ringing after agent unregistration ([#24908](https://github.com/RocketChat/Rocket.Chat/pull/24908)) + +- Regression: Custom roles displaying ID instead of name on some admin screens ([#24999](https://github.com/RocketChat/Rocket.Chat/pull/24999)) + + ![image](https://user-images.githubusercontent.com/55164754/160981416-555bcaa1-c075-4260-937c-64523472da43.png) + ![image](https://user-images.githubusercontent.com/55164754/160981452-6eae4e74-8425-4073-8256-472aba72b9db.png) + +- Regression: Error is raised when there's no Asterisk queue available yet ([#24980](https://github.com/RocketChat/Rocket.Chat/pull/24980)) + +- Regression: Fix account service login expiration ([#24920](https://github.com/RocketChat/Rocket.Chat/pull/24920)) + +- Regression: Fix ParentRoomWithEndpointData in loop ([#24809](https://github.com/RocketChat/Rocket.Chat/pull/24809)) + +- Regression: Fix unexpected errors breaking ddp-streamer ([#24948](https://github.com/RocketChat/Rocket.Chat/pull/24948)) + +- Regression: Improve Sidenav open/close handling and fixed codeql configs and E2E tests ([#24756](https://github.com/RocketChat/Rocket.Chat/pull/24756)) + +- Regression: Register services right away ([#24800](https://github.com/RocketChat/Rocket.Chat/pull/24800)) + +- Regression: Role Sync not always working ([#24850](https://github.com/RocketChat/Rocket.Chat/pull/24850)) + +
+ +### 👩‍💻👨‍💻 Contributors 😍 + +- [@Aman-Maheshwari](https://github.com/Aman-Maheshwari) +- [@Himanshu664](https://github.com/Himanshu664) +- [@JMoVS](https://github.com/JMoVS) +- [@Muramatsu2602](https://github.com/Muramatsu2602) +- [@aadishJ01](https://github.com/aadishJ01) +- [@aswinidev](https://github.com/aswinidev) +- [@dependabot[bot]](https://github.com/dependabot[bot]) +- [@eduardofcabrera](https://github.com/eduardofcabrera) +- [@nishant23122000](https://github.com/nishant23122000) +- [@ostjen](https://github.com/ostjen) +- [@tkurz](https://github.com/tkurz) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@KevLehman](https://github.com/KevLehman) +- [@MartinSchoeler](https://github.com/MartinSchoeler) +- [@albuquerquefabio](https://github.com/albuquerquefabio) +- [@amolghode1981](https://github.com/amolghode1981) +- [@cauefcr](https://github.com/cauefcr) +- [@debdutdeb](https://github.com/debdutdeb) +- [@dougfabris](https://github.com/dougfabris) +- [@felipe-rod123](https://github.com/felipe-rod123) +- [@filipemarins](https://github.com/filipemarins) +- [@gabriellsh](https://github.com/gabriellsh) +- [@geekgonecrazy](https://github.com/geekgonecrazy) +- [@gerzonc](https://github.com/gerzonc) +- [@ggazzo](https://github.com/ggazzo) +- [@juliajforesti](https://github.com/juliajforesti) +- [@matheusbsilva137](https://github.com/matheusbsilva137) +- [@murtaza98](https://github.com/murtaza98) +- [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) +- [@renatobecker](https://github.com/renatobecker) +- [@rodrigok](https://github.com/rodrigok) +- [@sampaiodiego](https://github.com/sampaiodiego) +- [@tassoevan](https://github.com/tassoevan) +- [@tiagoevanp](https://github.com/tiagoevanp) +- [@yash-rajpal](https://github.com/yash-rajpal) + +# 4.5.6 +`2022-04-07 · 2 🐛 · 2 👩‍💻👨‍💻` + +### Engine versions +- Node: `14.18.3` +- NPM: `6.14.15` +- MongoDB: `3.6, 4.0, 4.2, 4.4, 5.0` +- Apps-Engine: `1.31.0` + +### 🐛 Bug fixes + + +- NPS never finishing sending results ([#25067](https://github.com/RocketChat/Rocket.Chat/pull/25067)) + +- Proxy settings being ignored ([#25022](https://github.com/RocketChat/Rocket.Chat/pull/25022)) + + Modify Meteor's `HTTP.call` to add back proxy support + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) +- [@sampaiodiego](https://github.com/sampaiodiego) + +# 4.5.5 +`2022-03-30 · 2 🐛 · 2 🔍 · 6 👩‍💻👨‍💻` + +### Engine versions +- Node: `14.18.3` +- NPM: `6.14.15` +- MongoDB: `3.6, 4.0, 4.2, 4.4, 5.0` +- Apps-Engine: `1.31.0` + +### 🐛 Bug fixes + + +- High CPU usage caused by CallProvider ([#24994](https://github.com/RocketChat/Rocket.Chat/pull/24994)) + + Remove infinity loop inside useVoipClient hook. + + #closes #24970 + +- Multiple issues starting a new DM ([#24955](https://github.com/RocketChat/Rocket.Chat/pull/24955)) + + When the room object is searched for the first time, it does not exist on the front object yet (subscription), adding a fallback search for room list will guarantee to search the room details. + + before: + https://user-images.githubusercontent.com/9275105/160223241-d2319f3e-82c5-47d6-867f-695ab2361a17.mp4 + + after: + https://user-images.githubusercontent.com/9275105/160223244-84d0d2a1-3d95-464d-8b8a-e264b0d4d690.mp4 + +
+🔍 Minor changes + + +- Chore: Update Livechat ([#24990](https://github.com/RocketChat/Rocket.Chat/pull/24990)) + +- Release 4.5.5 ([#24998](https://github.com/RocketChat/Rocket.Chat/pull/24998)) + +
+ +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@MartinSchoeler](https://github.com/MartinSchoeler) +- [@filipemarins](https://github.com/filipemarins) +- [@ggazzo](https://github.com/ggazzo) +- [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) +- [@sampaiodiego](https://github.com/sampaiodiego) +- [@tiagoevanp](https://github.com/tiagoevanp) + +# 4.5.4 +`2022-03-24 · 1 🐛 · 1 🔍 · 3 👩‍💻👨‍💻` + +### Engine versions +- Node: `14.18.3` +- NPM: `6.14.15` +- MongoDB: `3.6, 4.0, 4.2, 4.4, 5.0` +- Apps-Engine: `1.31.0` + +### 🐛 Bug fixes + + +- SAML Force name to string ([#24930](https://github.com/RocketChat/Rocket.Chat/pull/24930)) + +
+🔍 Minor changes + + +- Release 4.5.4 ([#24938](https://github.com/RocketChat/Rocket.Chat/pull/24938)) + +
+ +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@AllanPazRibeiro](https://github.com/AllanPazRibeiro) +- [@geekgonecrazy](https://github.com/geekgonecrazy) +- [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) + +# 4.5.3 +`2022-03-21 · 2 🚀 · 8 🐛 · 1 🔍 · 5 👩‍💻👨‍💻` + +### Engine versions +- Node: `14.18.3` +- NPM: `6.14.15` +- MongoDB: `3.6, 4.0, 4.2, 4.4, 5.0` +- Apps-Engine: `1.31.0` + +### 🚀 Improvements + + +- Standarize queue behavior for managers and agents when subscribing ([#24837](https://github.com/RocketChat/Rocket.Chat/pull/24837)) + +- UX - VoIP Call Component ([#24748](https://github.com/RocketChat/Rocket.Chat/pull/24748)) + +### 🐛 Bug fixes + + +- **VOIP:** SidebarFooter component ([#24838](https://github.com/RocketChat/Rocket.Chat/pull/24838)) + + - Improve the CallProvider code; + - Adjust the text case of the VoIP component on the FooterSidebar; + - Fix the bad behavior with the changes in queue's name. + +- Broken build caused by PRs modifying same file differently ([#24863](https://github.com/RocketChat/Rocket.Chat/pull/24863)) + +- Custom script not being fired ([#24901](https://github.com/RocketChat/Rocket.Chat/pull/24901)) + +- Disable voip button when call is in progress ([#24864](https://github.com/RocketChat/Rocket.Chat/pull/24864)) + +- Show call icon only when user has extension associated ([#24752](https://github.com/RocketChat/Rocket.Chat/pull/24752)) + +- Show only enabled departments on forward ([#24829](https://github.com/RocketChat/Rocket.Chat/pull/24829)) + +- VoIP button gets disabled whenever user status changes ([#24789](https://github.com/RocketChat/Rocket.Chat/pull/24789)) + +- Wrong param usage on queue summary call ([#24799](https://github.com/RocketChat/Rocket.Chat/pull/24799)) + +
+🔍 Minor changes + + +- Chore: Fix MongoDB versions on release notes ([#24877](https://github.com/RocketChat/Rocket.Chat/pull/24877)) + +
+ +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@KevLehman](https://github.com/KevLehman) +- [@amolghode1981](https://github.com/amolghode1981) +- [@ggazzo](https://github.com/ggazzo) +- [@sampaiodiego](https://github.com/sampaiodiego) +- [@tiagoevanp](https://github.com/tiagoevanp) + +# 4.5.2 +`2022-03-12 · 1 🚀 · 7 🐛 · 1 🔍 · 8 👩‍💻👨‍💻` + +### Engine versions +- Node: `14.18.3` +- NPM: `6.14.15` +- MongoDB: `3.6, 4.0, 4.2, 4.4, 5.0` +- Apps-Engine: `1.31.0` + +### 🚀 Improvements + + +- Voip Extensions disabled state ([#24750](https://github.com/RocketChat/Rocket.Chat/pull/24750)) + +### 🐛 Bug fixes + + +- "livechat/webrtc.call" endpoint not working ([#24804](https://github.com/RocketChat/Rocket.Chat/pull/24804)) + +- `PaginatedSelectFiltered` not handling changes ([#24732](https://github.com/RocketChat/Rocket.Chat/pull/24732)) + +- Broken multiple OAuth integrations ([#24705](https://github.com/RocketChat/Rocket.Chat/pull/24705)) + +- Critical: Incorrect visitor getting assigned to a chat from apps ([#24805](https://github.com/RocketChat/Rocket.Chat/pull/24805)) + +- Opening a new DM from user card ([#24623](https://github.com/RocketChat/Rocket.Chat/pull/24623)) + + A race condition on `useRoomIcon` -- delayed merge of rooms and subscriptions -- was causing a UI crash whenever someone tried to open a DM from the user card component. + +- Revert AutoComplete ([#24812](https://github.com/RocketChat/Rocket.Chat/pull/24812)) + +- VoipExtensionsPage component call ([#24792](https://github.com/RocketChat/Rocket.Chat/pull/24792)) + +
+🔍 Minor changes + + +- Regression: Fix ParentRoomWithEndpointData in loop ([#24809](https://github.com/RocketChat/Rocket.Chat/pull/24809)) + +
+ +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@KevLehman](https://github.com/KevLehman) +- [@MartinSchoeler](https://github.com/MartinSchoeler) +- [@debdutdeb](https://github.com/debdutdeb) +- [@ggazzo](https://github.com/ggazzo) +- [@juliajforesti](https://github.com/juliajforesti) +- [@murtaza98](https://github.com/murtaza98) +- [@sampaiodiego](https://github.com/sampaiodiego) +- [@tassoevan](https://github.com/tassoevan) + +# 4.5.1 +`2022-03-09 · 13 🐛 · 2 🔍 · 12 👩‍💻👨‍💻` + +### Engine versions +- Node: `14.18.3` +- NPM: `6.14.15` +- MongoDB: `3.6, 4.0, 4.2, 4.4, 5.0` +- Apps-Engine: `1.31.0` + +### 🐛 Bug fixes + + +- Apple login script being loaded even when Apple Login is disabled. ([#24760](https://github.com/RocketChat/Rocket.Chat/pull/24760)) + +- Components for user search ([#24677](https://github.com/RocketChat/Rocket.Chat/pull/24677)) + +- Duplicated 'name' log key ([#24590](https://github.com/RocketChat/Rocket.Chat/pull/24590)) + +- Missing username on messages imported from Slack ([#24674](https://github.com/RocketChat/Rocket.Chat/pull/24674)) + + - Fix missing sender's username on messages imported from Slack. + +- no id of room closer in livechat-close message ([#24683](https://github.com/RocketChat/Rocket.Chat/pull/24683)) + +- Reload roomslist after successful deletion of a room from admin panel. ([#23795](https://github.com/RocketChat/Rocket.Chat/pull/23795) by [@Aman-Maheshwari](https://github.com/Aman-Maheshwari)) + + Removed the logic for calling the `rooms.adminRooms` endPoint from the `RoomsTable` Component and moved it to its parent component `RoomsPage`. + This allows to call the endPoint `rooms.adminRooms` from `EditRoomContextBar` Component which is also has `RoomPage` Component as its parent. + + Also added a succes toast message after the successful deletion of room. + +- Room's message count not being incremented on import ([#24696](https://github.com/RocketChat/Rocket.Chat/pull/24696)) + + - Fix rooms' message counter not being incremented on message import. + +- Show only available agents on extension association modal ([#24680](https://github.com/RocketChat/Rocket.Chat/pull/24680)) + +- System messages are sent when adding or removing a group from a team ([#24743](https://github.com/RocketChat/Rocket.Chat/pull/24743)) + + - Do not send system messages when adding or removing a new or existing _group_ from a team. + +- Typo and placeholder on wrap up call modal ([#24737](https://github.com/RocketChat/Rocket.Chat/pull/24737)) + +- Typo in wrap-up term ([#24661](https://github.com/RocketChat/Rocket.Chat/pull/24661)) + +- VoIP Enable/Disable setting on CallContext/CallProvider Notifications ([#24607](https://github.com/RocketChat/Rocket.Chat/pull/24607)) + +- Voip Stream Reinitialization Error ([#24657](https://github.com/RocketChat/Rocket.Chat/pull/24657)) + +
+🔍 Minor changes + + +- Chore: Update Livechat ([#24754](https://github.com/RocketChat/Rocket.Chat/pull/24754)) + +- Release 4.5.1 ([#24782](https://github.com/RocketChat/Rocket.Chat/pull/24782) by [@Aman-Maheshwari](https://github.com/Aman-Maheshwari) & [@cuonghuunguyen](https://github.com/cuonghuunguyen)) + +
+ +### 👩‍💻👨‍💻 Contributors 😍 + +- [@Aman-Maheshwari](https://github.com/Aman-Maheshwari) +- [@cuonghuunguyen](https://github.com/cuonghuunguyen) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@KevLehman](https://github.com/KevLehman) +- [@MartinSchoeler](https://github.com/MartinSchoeler) +- [@amolghode1981](https://github.com/amolghode1981) +- [@juliajforesti](https://github.com/juliajforesti) +- [@matheusbsilva137](https://github.com/matheusbsilva137) +- [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) +- [@renatobecker](https://github.com/renatobecker) +- [@sampaiodiego](https://github.com/sampaiodiego) +- [@tassoevan](https://github.com/tassoevan) +- [@tiagoevanp](https://github.com/tiagoevanp) + +# 4.5.0 +`2022-02-28 · 3 🎉 · 15 🚀 · 19 🐛 · 72 🔍 · 30 👩‍💻👨‍💻` + +### Engine versions +- Node: `14.18.3` +- NPM: `6.14.15` +- MongoDB: `3.6, 4.0, 4.2, 4.4, 5.0` +- Apps-Engine: `1.31.0` + +### 🎉 New features + + +- E2E password generator ([#24114](https://github.com/RocketChat/Rocket.Chat/pull/24114) by [@eduardofcabrera](https://github.com/eduardofcabrera) & [@ostjen](https://github.com/ostjen)) + +- Marketplace sort filter ([#24567](https://github.com/RocketChat/Rocket.Chat/pull/24567)) + + Implemented a sort filter for the marketplace screen. This component sorts the marketplace apps list in 4 ways, alphabetical order(A-Z), inverse alphabetical order(Z-A), most recently updated(MRU), and least recent updated(LRU). Besides that, I've generalized some components and types to increase code reusability, renamed some helpers as well as deleted some useless ones, and inserted the necessary new translations on the English i18n dictionary. + Demo gif: + ![Marketplace sort filter](https://user-images.githubusercontent.com/43561537/155033709-e07a6306-a85a-4f7f-9624-b53ba5dd7fa9.gif) + +- VoIP Support for Omnichannel ([#23102](https://github.com/RocketChat/Rocket.Chat/pull/23102)) + + - Created VoipService to manage VoIP connections and PBX connection + - Created LivechatVoipService that will handle custom cases for livechat (creating rooms, assigning chats to queue, actions when call is finished, etc) + - Created Basic interfaces to support new services and new model + - Created Endpoints for management interfaces + - Implemented asterisk connector on VoIP service + - Created UI components to show calls incoming and to allow answering/rejecting calls + - Added new settings to control call server/management server connection values + - Added endpoints to associate Omnichannel Agents with PBX Extensions + - Added support for event listening on server side, to get metadata about calls being received/ongoing + - Created new pages to update settings & to see user-extension association + - Created new page to see ongoing calls (and past calls) + - Added support for remote hangup/hold on calls + - Implemented call metrics calculation (hold time, waiting time, talk time) + - Show a notificaiton when call is received + +### 🚀 Improvements + + +- **ENTERPRISE:** Improve how micro services are loaded ([#24388](https://github.com/RocketChat/Rocket.Chat/pull/24388)) + +- Add return button in chats opened from the list of current chats ([#24458](https://github.com/RocketChat/Rocket.Chat/pull/24458) by [@LucasFASouza](https://github.com/LucasFASouza)) + + The new return button for Omnichannel chats came out with release 3.15 but the feature was only available for chats that were opened from Omnichannel Contact Center. + Now, the same UI/UX is supported for chats opened from Current Chats list. + + ![image](https://user-images.githubusercontent.com/32396925/153283190-bd5c9748-c36b-4874-a704-6043afc7e3a1.png) + + The chat now opens in the Omnichannel settings and has the return button so the user can go back to the Current Chats list. + + ![image](https://user-images.githubusercontent.com/32396925/153285591-fad8e4a0-d2ea-4a02-8b2a-15e383b3c876.png) + +- Add tooltips on action buttons of Canned Response message composer ([#24483](https://github.com/RocketChat/Rocket.Chat/pull/24483) by [@LucasFASouza](https://github.com/LucasFASouza)) + + The tooltips were missing on the action buttons of CR message composer. + + ![image](https://user-images.githubusercontent.com/32396925/153620327-91107245-4b47-4d39-a99a-6da6d1cf5734.png) + + Users can now feel more encouraged to use these actions knowing what they are supposed to do. + +- Add user to room on "Click to Join!" button press ([#24041](https://github.com/RocketChat/Rocket.Chat/pull/24041) by [@ostjen](https://github.com/ostjen)) + + - Add user to room on "Click to Join!" button press; + - Display the "Join" button in discussions inside channels (keeping the behavior consistent with discussions inside groups). + +- Added a new "All" tab which shows all integrations in Integrations ([#24109](https://github.com/RocketChat/Rocket.Chat/pull/24109) by [@aswinidev](https://github.com/aswinidev)) + +- ChatBox Text to File Description ([#24451](https://github.com/RocketChat/Rocket.Chat/pull/24451) by [@eduardofcabrera](https://github.com/eduardofcabrera) & [@ostjen](https://github.com/ostjen)) + + The text content from chatbox goes to the file description when drag and drop a file. + +- Close modal on esc and outside click ([#24275](https://github.com/RocketChat/Rocket.Chat/pull/24275)) + + This is a QUICK change in order to close modals pressing Esc button and clicking outside of it **intentionally**. + +- CloudLoginModal visual consistency ([#24334](https://github.com/RocketChat/Rocket.Chat/pull/24334)) + + ### before + ![image](https://user-images.githubusercontent.com/27704687/151585064-dc6a1e29-9903-4241-8fbd-dfbe6c55fbef.png) + + ### after + ![Screen Shot 2022-01-28 at 13 32 02](https://user-images.githubusercontent.com/27704687/151585101-75b98502-9aae-4198-bc3e-4956750e5d8b.png) + +- Convert tag edit with department data to tsx ([#24369](https://github.com/RocketChat/Rocket.Chat/pull/24369) by [@LucasFASouza](https://github.com/LucasFASouza)) + +- Descriptive tooltip for Encrypted Key on Room Header ([#24121](https://github.com/RocketChat/Rocket.Chat/pull/24121)) + +- OTR system messages ([#24382](https://github.com/RocketChat/Rocket.Chat/pull/24382)) + + OTR system messages to indicate key refresh and joining chat to users. + +- Purchase Type Filter for marketplace apps and Categories filter anchor refactoring ([#24454](https://github.com/RocketChat/Rocket.Chat/pull/24454)) + + Implemented a filter by purchase type(free or paid) component for the apps screen of the marketplace. Besides that, new entries on the dictionary, fixed some parts of the App type (purchaseType was typed as unknown and price as string), and created some helpers to work alongside the filter. Will be refactoring the categories filter anchor and then will open this PR for reviews. + + Demo gif: + ![purchaseTypeFIlter](https://user-images.githubusercontent.com/43561537/153101228-7b7ebdc3-2d34-420f-aa9d-f7cbc8d4b53f.gif) + + Refactored the categories filter anchor from a plain fuselage select to a select button with dynamic colors. + Demo gif: + ![New categories filter anchor(PR)](https://user-images.githubusercontent.com/43561537/153422427-28012b7d-e0ec-45f4-861d-c9368c57ad04.gif) + +- Replace AutoComplete in UserAutoComplete & UserAutoCompleteMultiple components ([#24529](https://github.com/RocketChat/Rocket.Chat/pull/24529)) + + This PR replaces a deprecated fuselage's component `AutoComplete` in favor of `Select` and `MultiSelect` which fixes some of UX/UI issues in selecting users + + ### before + ![Screen Shot 2022-02-19 at 13 33 28](https://user-images.githubusercontent.com/27704687/154809737-8181a06c-4f20-48ea-90f7-01e828b9a452.png) + + ### after + ![Screen Shot 2022-02-19 at 13 30 58](https://user-images.githubusercontent.com/27704687/154809653-a8ec9a80-c0dd-4a25-9c00-0f96147d79e9.png) + +- Skip encryption for slash commands in E2E rooms ([#24475](https://github.com/RocketChat/Rocket.Chat/pull/24475)) + + Currently Slash Commands don't work in an E2EE room, as we encrypt the message before slash command is detected by the server, So removed encryption for slash commands in e2e rooms. + +- Team system messages feedback ([#24209](https://github.com/RocketChat/Rocket.Chat/pull/24209) by [@ostjen](https://github.com/ostjen)) + + - Delete some keys that aren't being used (eg: User_left_female). + - Add new Teams' system messages: + - `added-user-to-team`: **added** @\user to this Team; + - `removed-user-from-team`: **removed** @\user from this Team; + - `user-converted-to-team`: **converted** #\room to a Team; + - `user-converted-to-channel`: **converted** #\room to a Channel; + - `user-removed-room-from-team`: **removed** @\user from this Team; + - `user-deleted-room-from-team`: **deleted** #\room from this Team; + - `user-added-room-to-team`: **deleted** #\room to this Team; + - Add the corresponding options to hide each new system message and the missing `ujt` and `ult` hide options. + +### 🐛 Bug fixes + + +- 2FA via email when logging in using OAuth ([#24572](https://github.com/RocketChat/Rocket.Chat/pull/24572)) + +- Add ?close to OAuth callback url ([#24381](https://github.com/RocketChat/Rocket.Chat/pull/24381)) + +- GDPR action to forget visitor data on request ([#24441](https://github.com/RocketChat/Rocket.Chat/pull/24441)) + +- Implement client errors on ddp-streamer ([#24310](https://github.com/RocketChat/Rocket.Chat/pull/24310)) + +- Inconsistent validation of user's access to rooms ([#24037](https://github.com/RocketChat/Rocket.Chat/pull/24037) by [@ostjen](https://github.com/ostjen)) + +- Issues on selecting users when importing CSV ([#24253](https://github.com/RocketChat/Rocket.Chat/pull/24253)) + + * Fix users selecting by fixing their _id + * Add condition to disable 'Start importing' button if `usersCount`, `channelsCount` and `messageCount` equals 0, or if messageCount is alone + * Remove `disabled={usersCount === 0}` on user Tab + +- OAuth mismatch redirect_uri error ([#24450](https://github.com/RocketChat/Rocket.Chat/pull/24450)) + +- Oembed request not respecting payload limit ([#24418](https://github.com/RocketChat/Rocket.Chat/pull/24418)) + +- Omnichannel managers can't join chats in progress ([#24553](https://github.com/RocketChat/Rocket.Chat/pull/24553)) + +- Outgoing webhook without scripts not saving messages ([#24401](https://github.com/RocketChat/Rocket.Chat/pull/24401)) + +- Prevent Apps Bridge to remove visitor status from room ([#24305](https://github.com/RocketChat/Rocket.Chat/pull/24305)) + +- Read receipts showing first messages of the room as read even if not read by everyone ([#24508](https://github.com/RocketChat/Rocket.Chat/pull/24508)) + +- respect `Accounts_Registration_Users_Default_Roles` setting ([#24173](https://github.com/RocketChat/Rocket.Chat/pull/24173)) + + - Fix `user` role being added as default regardless of the `Accounts_Registration_Users_Default_Roles` setting. + +- Room context tabs not working in Omnichannel current chats page ([#24559](https://github.com/RocketChat/Rocket.Chat/pull/24559)) + +- Skip admin info in setup wizard for servers with admin registered ([#24485](https://github.com/RocketChat/Rocket.Chat/pull/24485)) + +- Skip cloud steps for registered servers on setup wizard ([#24407](https://github.com/RocketChat/Rocket.Chat/pull/24407)) + +- Slash commands previews not working ([#24387](https://github.com/RocketChat/Rocket.Chat/pull/24387) by [@ostjen](https://github.com/ostjen)) + +- Startup errors creating indexes ([#24409](https://github.com/RocketChat/Rocket.Chat/pull/24409)) + + Fix `bio` and `prid` startup index creation errors. + +- typo on register server tooltip of setup wizard ([#24466](https://github.com/RocketChat/Rocket.Chat/pull/24466)) + +
+🔍 Minor changes + + +- Bump @types/ws from 8.2.2 to 8.2.3 in /ee/server/services ([#24556](https://github.com/RocketChat/Rocket.Chat/pull/24556) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump adm-zip from 0.4.14 to 0.5.9 ([#24538](https://github.com/RocketChat/Rocket.Chat/pull/24538) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump body-parser from 1.19.0 to 1.19.1 in /ee/server/services ([#23963](https://github.com/RocketChat/Rocket.Chat/pull/23963) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump body-parser from 1.19.1 to 1.19.2 in /ee/server/services ([#24517](https://github.com/RocketChat/Rocket.Chat/pull/24517) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump cookie from 0.4.1 to 0.4.2 in /ee/server/services ([#24472](https://github.com/RocketChat/Rocket.Chat/pull/24472) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump date-fns from 2.24.0 to 2.28.0 ([#24058](https://github.com/RocketChat/Rocket.Chat/pull/24058) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump express from 4.17.1 to 4.17.2 in /ee/server/services ([#24469](https://github.com/RocketChat/Rocket.Chat/pull/24469) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump express from 4.17.2 to 4.17.3 in /ee/server/services ([#24522](https://github.com/RocketChat/Rocket.Chat/pull/24522) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump follow-redirects from 1.14.7 to 1.14.8 in /ee/server/services ([#24491](https://github.com/RocketChat/Rocket.Chat/pull/24491) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump jaeger-client from 3.18.1 to 3.19.0 in /ee/server/services ([#23961](https://github.com/RocketChat/Rocket.Chat/pull/23961) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump pm2 from 5.1.2 to 5.2.0 in /ee/server/services ([#24537](https://github.com/RocketChat/Rocket.Chat/pull/24537) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump simple-get from 4.0.0 to 4.0.1 ([#24341](https://github.com/RocketChat/Rocket.Chat/pull/24341) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump sodium-native from 3.2.1 to 3.3.0 in /ee/server/services ([#23512](https://github.com/RocketChat/Rocket.Chat/pull/23512) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump underscore.string from 3.3.5 to 3.3.6 in /ee/server/services ([#24498](https://github.com/RocketChat/Rocket.Chat/pull/24498) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump url-parse from 1.5.3 to 1.5.7 ([#24528](https://github.com/RocketChat/Rocket.Chat/pull/24528) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Bump vm2 from 3.9.5 to 3.9.7 in /ee/server/services ([#24509](https://github.com/RocketChat/Rocket.Chat/pull/24509) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Chore: `twoFactorRequired` signature ([#24518](https://github.com/RocketChat/Rocket.Chat/pull/24518)) + + Improved type checking for decorator `twoFactorRequired`. + +- Chore: Add description to global OTR setting ([#24333](https://github.com/RocketChat/Rocket.Chat/pull/24333) by [@pedrogssouza](https://github.com/pedrogssouza)) + +- Chore: Bump Fuselage packages ([#24573](https://github.com/RocketChat/Rocket.Chat/pull/24573)) + + It uses the last stable version of Fuselage packages. + +- Chore: bump fuselage version ([#24453](https://github.com/RocketChat/Rocket.Chat/pull/24453)) + +- Chore: Convert JS files to Typescript ([#24410](https://github.com/RocketChat/Rocket.Chat/pull/24410)) + + This pull request converts 26 more files from Javascript to Typescript, to check variable types and increase validation on the code. + +- Chore: Convert to typescript the me slashCommands files ([#24321](https://github.com/RocketChat/Rocket.Chat/pull/24321) by [@eduardofcabrera](https://github.com/eduardofcabrera) & [@ostjen](https://github.com/ostjen)) + + Convert to typescript the me slashCommands files + +- Chore: Convert to typescript the mute and unmute slash commands files ([#24325](https://github.com/RocketChat/Rocket.Chat/pull/24325) by [@eduardofcabrera](https://github.com/eduardofcabrera) & [@ostjen](https://github.com/ostjen)) + + Convert to typescript the mute and unmute slash commands files + +- Chore: Convert to typescript the slash commands create files ([#24306](https://github.com/RocketChat/Rocket.Chat/pull/24306) by [@eduardofcabrera](https://github.com/eduardofcabrera) & [@ostjen](https://github.com/ostjen)) + + Convert Slash Commands create files to typescript. + +- Chore: Convert to typescript the slash commands invite files ([#24311](https://github.com/RocketChat/Rocket.Chat/pull/24311) by [@eduardofcabrera](https://github.com/eduardofcabrera) & [@ostjen](https://github.com/ostjen)) + + Convert to typescript the slash commands invite files + +- Chore: Convert to typescript the unarchive slash commands files ([#24331](https://github.com/RocketChat/Rocket.Chat/pull/24331) by [@eduardofcabrera](https://github.com/eduardofcabrera) & [@ostjen](https://github.com/ostjen)) + + Convert to typescript the unarchive slash commands files + +- Chore: Delete unused file (NewAdminInfoPage.js) ([#24196](https://github.com/RocketChat/Rocket.Chat/pull/24196)) + + Just removing a duplicated/unused file. + +- Chore: Improve PR title validation regex ([#24467](https://github.com/RocketChat/Rocket.Chat/pull/24467)) + +- Chore: Js to ts slash commands archive ([#24304](https://github.com/RocketChat/Rocket.Chat/pull/24304) by [@eduardofcabrera](https://github.com/eduardofcabrera)) + + Convert Slash Commands archive files to typescript + +- Chore: Remove storybook build job from CI ([#24530](https://github.com/RocketChat/Rocket.Chat/pull/24530)) + +- Chore: roomTypes: Stop mixing client and server code together ([#24536](https://github.com/RocketChat/Rocket.Chat/pull/24536)) + +- Chore: Run tests using microservices deployment on CI ([#24513](https://github.com/RocketChat/Rocket.Chat/pull/24513)) + +- Chore: Set Docker image tag to latest only when really latest ([#24366](https://github.com/RocketChat/Rocket.Chat/pull/24366)) + +- Chore: Unify ILivechatAgent with ILivechatAgentRecord ([#24406](https://github.com/RocketChat/Rocket.Chat/pull/24406)) + +- Chore: Update Apps-Engine ([#24568](https://github.com/RocketChat/Rocket.Chat/pull/24568)) + +- Chore: Update Apps-Engine ([#24651](https://github.com/RocketChat/Rocket.Chat/pull/24651)) + +- Chore: Update fuselage deps to match monolith versions ([#24501](https://github.com/RocketChat/Rocket.Chat/pull/24501)) + +- Chore: Update Meteor to 2.5.6 ([#24461](https://github.com/RocketChat/Rocket.Chat/pull/24461)) + +- Chore: Update ws package ([#24477](https://github.com/RocketChat/Rocket.Chat/pull/24477)) + +- Chore(deps-dev): Bump ts-node from 10.0.0 to 10.5.0 in /ee/server/services ([#24435](https://github.com/RocketChat/Rocket.Chat/pull/24435) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- Chore(deps): Bump node-fetch from 2.6.1 to 2.6.7 in /ee/server/services ([#24299](https://github.com/RocketChat/Rocket.Chat/pull/24299) by [@dependabot[bot]](https://github.com/dependabot[bot])) + +- i18n: Language update from LingoHub 🤖 on 2022-01-31Z ([#24357](https://github.com/RocketChat/Rocket.Chat/pull/24357)) + +- i18n: Language update from LingoHub 🤖 on 2022-02-07Z ([#24429](https://github.com/RocketChat/Rocket.Chat/pull/24429)) + +- i18n: Language update from LingoHub 🤖 on 2022-02-14Z ([#24493](https://github.com/RocketChat/Rocket.Chat/pull/24493)) + +- i18n: Language update from LingoHub 🤖 on 2022-02-21Z ([#24558](https://github.com/RocketChat/Rocket.Chat/pull/24558)) + +- Merge master into develop & Set version to 4.5.0-develop ([#24363](https://github.com/RocketChat/Rocket.Chat/pull/24363)) + +- Regression: Add support to namespace within micro services ([#24581](https://github.com/RocketChat/Rocket.Chat/pull/24581)) + +- Regression: Admin Sidebar colors inverted. ([#24609](https://github.com/RocketChat/Rocket.Chat/pull/24609)) + +- Regression: Bunch of settings fixes for VoIP ([#24594](https://github.com/RocketChat/Rocket.Chat/pull/24594)) + +- Regression: Do not show toast on incoming voip calls ([#24619](https://github.com/RocketChat/Rocket.Chat/pull/24619)) + +- Regression: Encode registration info as JWT when signing key is provided ([#24626](https://github.com/RocketChat/Rocket.Chat/pull/24626)) + +- Regression: Error setting user avatars and mentioning rooms on Slack Import ([#24585](https://github.com/RocketChat/Rocket.Chat/pull/24585)) + + - Fix `Mentioned room not found` error when importing rooms from Slack; + - Fix `Forbidden` error when setting avatars for users imported from Slack (on user import/creation); + - Fix incorrect message count on imported rooms; + - Fix missing username on messages imported from Slack; + +- Regression: Error when trying to load name of dm rooms for avatars and notifications ([#24583](https://github.com/RocketChat/Rocket.Chat/pull/24583)) + +- Regression: Extension List panel UI not aligned with designs ([#24645](https://github.com/RocketChat/Rocket.Chat/pull/24645)) + +- Regression: Fix double value on holdTime and empty msg on last message ([#24630](https://github.com/RocketChat/Rocket.Chat/pull/24630)) + +- Regression: Fix in-correct room status shown to agents ([#24592](https://github.com/RocketChat/Rocket.Chat/pull/24592)) + +- Regression: Fix incoming voip call ringtone is not ringing ([#24616](https://github.com/RocketChat/Rocket.Chat/pull/24616)) + +- Regression: Fix room not getting created due to null visitor status ([#24562](https://github.com/RocketChat/Rocket.Chat/pull/24562)) + +- Regression: Fix time fields and wrap up in Voip Room Contexual bar ([#24625](https://github.com/RocketChat/Rocket.Chat/pull/24625)) + +- Regression: Fix time format on Voip system messages ([#24603](https://github.com/RocketChat/Rocket.Chat/pull/24603)) + +- Regression: Fix translation for call started message ([#24615](https://github.com/RocketChat/Rocket.Chat/pull/24615)) + +- Regression: Fix wrong tab name for VoIP settings ([#24647](https://github.com/RocketChat/Rocket.Chat/pull/24647)) + +- Regression: Fixes in Voice Contextual Bar and Directory ([#24596](https://github.com/RocketChat/Rocket.Chat/pull/24596)) + +- Regression: If Asterisk suddenly goes down, server has no way to know. Causes server to get stuck. Needs restart ([#24624](https://github.com/RocketChat/Rocket.Chat/pull/24624)) + +- Regression: Mark all rooms as read modal closing instantly. ([#24610](https://github.com/RocketChat/Rocket.Chat/pull/24610)) + +- Regression: No audio when call comes from Skype/IP phone ([#24602](https://github.com/RocketChat/Rocket.Chat/pull/24602)) + + The audio was not rendered because of re-rendering of react element based on + queueCounter and roomInfo. queueCounter and roomInfo cause the dom to re-render when call gets accepted + because after accepting call, queueCounter changes or a room gets created. + The audio element gets recreated. But VoIP user probably holds the old one. + The behaviour is not predictable when such case happens. If everything gets cleanly setup, + even if the audio element goes headless, it still continues to play the remote audio. + But in other cases, it is unreferenced the one on dom has its srcObject as null. + This causes no audio. + + This fix provides a way to re-initialise the rendering elements in VoIP user + and calls this function on useEffect() if the re-render has happen. + +- Regression: Prevent button from losing state when rerendering ([#24648](https://github.com/RocketChat/Rocket.Chat/pull/24648)) + +- Regression: Prevent connect to asterisk when VoIP is disabled ([#24601](https://github.com/RocketChat/Rocket.Chat/pull/24601)) + +- Regression: Queue counter aggregator for incoming/hanged calls ([#24635](https://github.com/RocketChat/Rocket.Chat/pull/24635)) + +- Regression: Refresh server connection when MI server settings change ([#24649](https://github.com/RocketChat/Rocket.Chat/pull/24649)) + +- Regression: Server crashing if Voip credentials are invalid ([#24646](https://github.com/RocketChat/Rocket.Chat/pull/24646)) + +- Regression: VoIP service button displayed when VoIP is disabled ([#24598](https://github.com/RocketChat/Rocket.Chat/pull/24598)) + +
+ +### 👩‍💻👨‍💻 Contributors 😍 + +- [@LucasFASouza](https://github.com/LucasFASouza) +- [@aswinidev](https://github.com/aswinidev) +- [@dependabot[bot]](https://github.com/dependabot[bot]) +- [@eduardofcabrera](https://github.com/eduardofcabrera) +- [@ostjen](https://github.com/ostjen) +- [@pedrogssouza](https://github.com/pedrogssouza) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@KevLehman](https://github.com/KevLehman) +- [@MartinSchoeler](https://github.com/MartinSchoeler) +- [@albuquerquefabio](https://github.com/albuquerquefabio) +- [@amolghode1981](https://github.com/amolghode1981) +- [@d-gubert](https://github.com/d-gubert) +- [@debdutdeb](https://github.com/debdutdeb) +- [@dougfabris](https://github.com/dougfabris) +- [@felipe-rod123](https://github.com/felipe-rod123) +- [@filipemarins](https://github.com/filipemarins) +- [@gabriellsh](https://github.com/gabriellsh) +- [@ggazzo](https://github.com/ggazzo) +- [@guijun13](https://github.com/guijun13) +- [@juliajforesti](https://github.com/juliajforesti) +- [@matheusbsilva137](https://github.com/matheusbsilva137) +- [@murtaza98](https://github.com/murtaza98) +- [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) +- [@renatobecker](https://github.com/renatobecker) +- [@rique223](https://github.com/rique223) +- [@rodrigok](https://github.com/rodrigok) +- [@sampaiodiego](https://github.com/sampaiodiego) +- [@tassoevan](https://github.com/tassoevan) +- [@tiagoevanp](https://github.com/tiagoevanp) +- [@ujorgeleite](https://github.com/ujorgeleite) +- [@yash-rajpal](https://github.com/yash-rajpal) + +# 4.4.3 +`2022-04-07 · 2 🐛 · 2 👩‍💻👨‍💻` + +### Engine versions +- Node: `14.18.2` +- NPM: `6.14.15` +- MongoDB: `3.6, 4.0, 4.2, 4.4, 5.0` +- Apps-Engine: `1.30.0` + +### 🐛 Bug fixes + + +- NPS never finishing sending results ([#25067](https://github.com/RocketChat/Rocket.Chat/pull/25067)) + +- Proxy settings being ignored ([#25022](https://github.com/RocketChat/Rocket.Chat/pull/25022)) + + Modify Meteor's `HTTP.call` to add back proxy support + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) +- [@sampaiodiego](https://github.com/sampaiodiego) + # 4.4.2 -`2022-02-09 · 1 🐛 · 1 🔍 · 2 👩‍💻👨‍💻` +`2022-02-09 · 1 🐛 · 2 🔍 · 3 👩‍💻👨‍💻` ### Engine versions - Node: `14.18.2` @@ -19,11 +1237,14 @@ - Chore: bump fuselage version ([#24453](https://github.com/RocketChat/Rocket.Chat/pull/24453)) +- Release 4.4.2 ([#24459](https://github.com/RocketChat/Rocket.Chat/pull/24459)) + ### 👩‍💻👨‍💻 Core Team 🤓 - [@dougfabris](https://github.com/dougfabris) +- [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) - [@sampaiodiego](https://github.com/sampaiodiego) # 4.4.1 @@ -46,7 +1267,7 @@ - Skip cloud steps for registered servers on setup wizard ([#24407](https://github.com/RocketChat/Rocket.Chat/pull/24407)) -- Slash commands previews not working ([#24387](https://github.com/RocketChat/Rocket.Chat/pull/24387)) +- Slash commands previews not working ([#24387](https://github.com/RocketChat/Rocket.Chat/pull/24387) by [@ostjen](https://github.com/ostjen)) - Startup errors creating indexes ([#24409](https://github.com/RocketChat/Rocket.Chat/pull/24409)) @@ -56,15 +1277,18 @@ 🔍 Minor changes -- Release 4.4.1 ([#24432](https://github.com/RocketChat/Rocket.Chat/pull/24432)) +- Release 4.4.1 ([#24432](https://github.com/RocketChat/Rocket.Chat/pull/24432) by [@ostjen](https://github.com/ostjen)) +### 👩‍💻👨‍💻 Contributors 😍 + +- [@ostjen](https://github.com/ostjen) + ### 👩‍💻👨‍💻 Core Team 🤓 - [@dougfabris](https://github.com/dougfabris) - [@gabriellsh](https://github.com/gabriellsh) -- [@ostjen](https://github.com/ostjen) - [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) - [@sampaiodiego](https://github.com/sampaiodiego) - [@tassoevan](https://github.com/tassoevan) @@ -125,9 +1349,9 @@ ![Screen Shot 2022-01-13 at 13 38 59](https://user-images.githubusercontent.com/27704687/149371232-3d292f5e-e8b0-41e1-b065-90a80a5f08ce.png) ![Screen Shot 2022-01-13 at 13 39 08](https://user-images.githubusercontent.com/27704687/149371263-64fd09e4-456e-48ee-9976-83f42b90e4d9.png) -- Importer text for CSV upload file format ([#23817](https://github.com/RocketChat/Rocket.Chat/pull/23817)) +- Importer text for CSV upload file format ([#23817](https://github.com/RocketChat/Rocket.Chat/pull/23817) by [@ostjen](https://github.com/ostjen)) -- lib/Statistics improved and metrics collector ([#24177](https://github.com/RocketChat/Rocket.Chat/pull/24177)) +- lib/Statistics improved and metrics collector ([#24177](https://github.com/RocketChat/Rocket.Chat/pull/24177) by [@ostjen](https://github.com/ostjen)) - On `statistics` object the property `get` is an async function now. - We need to collect additional data of feature activation through the statistics collector. @@ -247,7 +1471,7 @@ Right now, if we try to press enter for a new line on multi-line modal input... it auto triggers the submit event. This PR fixes this behaviour by not submitting the modal in case the enter was pressed within an input text with multiline expected -- Errors on advanced sync prevent LDAP users from logging in ([#23958](https://github.com/RocketChat/Rocket.Chat/pull/23958)) +- Errors on advanced sync prevent LDAP users from logging in ([#23958](https://github.com/RocketChat/Rocket.Chat/pull/23958) by [@ostjen](https://github.com/ostjen)) - Filter ability for admin room checkboxes ([#23970](https://github.com/RocketChat/Rocket.Chat/pull/23970) by [@sidmohanty11](https://github.com/sidmohanty11)) @@ -337,7 +1561,7 @@ It replaces some templates used by login and invitation flows with React components. It also drops `main` template, allowing `appLayout` to just handle components now. -- Chore: Slash Commands Join to Typescript ([#24254](https://github.com/RocketChat/Rocket.Chat/pull/24254)) +- Chore: Slash Commands Join to Typescript ([#24254](https://github.com/RocketChat/Rocket.Chat/pull/24254) by [@eduardofcabrera](https://github.com/eduardofcabrera) & [@ostjen](https://github.com/ostjen)) Convert the slash commands .js files to .ts files. @@ -418,9 +1642,11 @@ - [@arshxyz](https://github.com/arshxyz) - [@aswinidev](https://github.com/aswinidev) - [@dependabot[bot]](https://github.com/dependabot[bot]) +- [@eduardofcabrera](https://github.com/eduardofcabrera) - [@grahhnt](https://github.com/grahhnt) - [@mbreslein-thd](https://github.com/mbreslein-thd) - [@nishant23122000](https://github.com/nishant23122000) +- [@ostjen](https://github.com/ostjen) - [@sidmohanty11](https://github.com/sidmohanty11) ### 👩‍💻👨‍💻 Core Team 🤓 @@ -433,14 +1659,12 @@ - [@d-gubert](https://github.com/d-gubert) - [@debdutdeb](https://github.com/debdutdeb) - [@dougfabris](https://github.com/dougfabris) -- [@eduardofcabrera](https://github.com/eduardofcabrera) - [@gabriellsh](https://github.com/gabriellsh) - [@geekgonecrazy](https://github.com/geekgonecrazy) - [@ggazzo](https://github.com/ggazzo) - [@juliajforesti](https://github.com/juliajforesti) - [@matheusbsilva137](https://github.com/matheusbsilva137) - [@murtaza98](https://github.com/murtaza98) -- [@ostjen](https://github.com/ostjen) - [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) - [@renatobecker](https://github.com/renatobecker) - [@rique223](https://github.com/rique223) @@ -664,7 +1888,7 @@ - DMs being created with username instead of user's name ([#23848](https://github.com/RocketChat/Rocket.Chat/pull/23848)) -- Email notifications settings not being honored on new DMs ([#23574](https://github.com/RocketChat/Rocket.Chat/pull/23574)) +- Email notifications settings not being honored on new DMs ([#23574](https://github.com/RocketChat/Rocket.Chat/pull/23574) by [@ostjen](https://github.com/ostjen)) - Error when creating an inactive user in admin panel ([#23859](https://github.com/RocketChat/Rocket.Chat/pull/23859)) @@ -768,7 +1992,7 @@ - Bump thehanimo/pr-title-checker from 1.2 to 1.3.4 ([#23853](https://github.com/RocketChat/Rocket.Chat/pull/23853) by [@dependabot[bot]](https://github.com/dependabot[bot])) -- Chore: added last login to users.list ([#23846](https://github.com/RocketChat/Rocket.Chat/pull/23846)) +- Chore: added last login to users.list ([#23846](https://github.com/RocketChat/Rocket.Chat/pull/23846) by [@ostjen](https://github.com/ostjen)) - Chore: Bump fuselage 0.31.0 ([#24046](https://github.com/RocketChat/Rocket.Chat/pull/24046)) @@ -784,7 +2008,7 @@ - Create NPM script to add new migrations - TODO: Infer next migration number from file list -- Chore: Deleted LivechatPageVisited ([#23993](https://github.com/RocketChat/Rocket.Chat/pull/23993)) +- Chore: Deleted LivechatPageVisited ([#23993](https://github.com/RocketChat/Rocket.Chat/pull/23993) by [@ostjen](https://github.com/ostjen)) - Chore: Enable prefer-optional-chain ESLint rule for TypeScript files ([#23786](https://github.com/RocketChat/Rocket.Chat/pull/23786)) @@ -884,6 +2108,7 @@ - [@aswinidev](https://github.com/aswinidev) - [@dependabot[bot]](https://github.com/dependabot[bot]) - [@goyome](https://github.com/goyome) +- [@ostjen](https://github.com/ostjen) - [@qwertiko](https://github.com/qwertiko) - [@rafaelblink](https://github.com/rafaelblink) - [@sidmohanty11](https://github.com/sidmohanty11) @@ -902,7 +2127,6 @@ - [@juliajforesti](https://github.com/juliajforesti) - [@matheusbsilva137](https://github.com/matheusbsilva137) - [@murtaza98](https://github.com/murtaza98) -- [@ostjen](https://github.com/ostjen) - [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) - [@renatobecker](https://github.com/renatobecker) - [@rique223](https://github.com/rique223) @@ -1021,13 +2245,13 @@ Open the Enterprise LDAP API that executes background sync to be used without any Enterprise License and enforce 2FA requirements. -- Permission for download/uploading files on mobile ([#23686](https://github.com/RocketChat/Rocket.Chat/pull/23686)) +- Permission for download/uploading files on mobile ([#23686](https://github.com/RocketChat/Rocket.Chat/pull/23686) by [@ostjen](https://github.com/ostjen)) - Permissions for interacting with Omnichannel Contact Center ([#23389](https://github.com/RocketChat/Rocket.Chat/pull/23389)) Adds a new permission, one that allows for control over user access to Omnichannel Contact Center, -- Rate limiting for user registering ([#23732](https://github.com/RocketChat/Rocket.Chat/pull/23732)) +- Rate limiting for user registering ([#23732](https://github.com/RocketChat/Rocket.Chat/pull/23732) by [@ostjen](https://github.com/ostjen)) - REST endpoints to manage Omnichannel Business Units ([#23750](https://github.com/RocketChat/Rocket.Chat/pull/23750)) @@ -1084,7 +2308,7 @@ ### 🐛 Bug fixes -- "to users" not working in export message ([#23576](https://github.com/RocketChat/Rocket.Chat/pull/23576)) +- "to users" not working in export message ([#23576](https://github.com/RocketChat/Rocket.Chat/pull/23576) by [@ostjen](https://github.com/ostjen)) - **ENTERPRISE:** OAuth "Merge Roles" removes roles from users ([#23588](https://github.com/RocketChat/Rocket.Chat/pull/23588)) @@ -1124,7 +2348,7 @@ - Fix typo in FR translation ([#23711](https://github.com/RocketChat/Rocket.Chat/pull/23711) by [@Cormoran96](https://github.com/Cormoran96)) -- Fixed E2E default room settings not being honoured ([#23468](https://github.com/RocketChat/Rocket.Chat/pull/23468) by [@TheDigitalEagle](https://github.com/TheDigitalEagle)) +- Fixed E2E default room settings not being honoured ([#23468](https://github.com/RocketChat/Rocket.Chat/pull/23468) by [@TheDigitalEagle](https://github.com/TheDigitalEagle) & [@ostjen](https://github.com/ostjen)) - LDAP users being disabled when an AD security policy is enabled ([#23820](https://github.com/RocketChat/Rocket.Chat/pull/23820)) @@ -1164,7 +2388,7 @@ When you take an Omnichannel chat from queue, the guest's typing information will appear. -- Registration not possible when any user is blocked for multiple failed logins ([#23565](https://github.com/RocketChat/Rocket.Chat/pull/23565)) +- Registration not possible when any user is blocked for multiple failed logins ([#23565](https://github.com/RocketChat/Rocket.Chat/pull/23565) by [@ostjen](https://github.com/ostjen))
🔍 Minor changes @@ -1265,6 +2489,7 @@ - [@TheDigitalEagle](https://github.com/TheDigitalEagle) - [@bhardwajaditya](https://github.com/bhardwajaditya) - [@dhruvjain99](https://github.com/dhruvjain99) +- [@ostjen](https://github.com/ostjen) ### 👩‍💻👨‍💻 Core Team 🤓 @@ -1278,7 +2503,6 @@ - [@ggazzo](https://github.com/ggazzo) - [@matheusbsilva137](https://github.com/matheusbsilva137) - [@murtaza98](https://github.com/murtaza98) -- [@ostjen](https://github.com/ostjen) - [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) - [@renatobecker](https://github.com/renatobecker) - [@rodrigok](https://github.com/rodrigok) @@ -1370,7 +2594,7 @@ Since now we're supporting markdown text on this field (via this PR - https://github.com/RocketChat/Rocket.Chat.Livechat/pull/648), it would be nice to make this setting multiline so users can have more space to edit the text ![image](https://user-images.githubusercontent.com/34130764/138146712-13e4968b-5312-4d53-b44c-b5699c5e49c1.png) -- optimized groups.listAll response time ([#22941](https://github.com/RocketChat/Rocket.Chat/pull/22941)) +- optimized groups.listAll response time ([#22941](https://github.com/RocketChat/Rocket.Chat/pull/22941) by [@ostjen](https://github.com/ostjen)) groups.listAll endpoint was having performance issues, specially when the total number of groups was high. This happened because the endpoint was loading all objects in memory then using splice to paginate, instead of paginating beforehand. @@ -1398,7 +2622,7 @@ - Attachment buttons overlap in mobile view ([#23377](https://github.com/RocketChat/Rocket.Chat/pull/23377) by [@Aman-Maheshwari](https://github.com/Aman-Maheshwari)) -- Avoid last admin deactivate itself ([#22949](https://github.com/RocketChat/Rocket.Chat/pull/22949)) +- Avoid last admin deactivate itself ([#22949](https://github.com/RocketChat/Rocket.Chat/pull/22949) by [@ostjen](https://github.com/ostjen)) Co-authored-by: @Kartik18g @@ -1408,7 +2632,7 @@ - Delay start of email inbox ([#23521](https://github.com/RocketChat/Rocket.Chat/pull/23521)) -- imported migration v240 ([#23374](https://github.com/RocketChat/Rocket.Chat/pull/23374)) +- imported migration v240 ([#23374](https://github.com/RocketChat/Rocket.Chat/pull/23374) by [@ostjen](https://github.com/ostjen)) - LDAP not stoping after wrong password ([#23382](https://github.com/RocketChat/Rocket.Chat/pull/23382)) @@ -1451,7 +2675,7 @@ - Server crashing when Routing method is not available at start ([#23473](https://github.com/RocketChat/Rocket.Chat/pull/23473)) -- unwanted toastr error message when deleting user ([#23372](https://github.com/RocketChat/Rocket.Chat/pull/23372)) +- unwanted toastr error message when deleting user ([#23372](https://github.com/RocketChat/Rocket.Chat/pull/23372) by [@ostjen](https://github.com/ostjen)) - useEndpointAction replace by useEndpointActionExperimental ([#23469](https://github.com/RocketChat/Rocket.Chat/pull/23469)) @@ -1586,6 +2810,7 @@ - [@badbart](https://github.com/badbart) - [@cuonghuunguyen](https://github.com/cuonghuunguyen) - [@dependabot[bot]](https://github.com/dependabot[bot]) +- [@ostjen](https://github.com/ostjen) - [@wolbernd](https://github.com/wolbernd) ### 👩‍💻👨‍💻 Core Team 🤓 @@ -1599,7 +2824,6 @@ - [@ggazzo](https://github.com/ggazzo) - [@matheusbsilva137](https://github.com/matheusbsilva137) - [@murtaza98](https://github.com/murtaza98) -- [@ostjen](https://github.com/ostjen) - [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) - [@rodrigok](https://github.com/rodrigok) - [@sampaiodiego](https://github.com/sampaiodiego) @@ -1759,7 +2983,7 @@ Fixes BigBlueButton integration -- imported migration v240 ([#23374](https://github.com/RocketChat/Rocket.Chat/pull/23374)) +- imported migration v240 ([#23374](https://github.com/RocketChat/Rocket.Chat/pull/23374) by [@ostjen](https://github.com/ostjen)) - LDAP not stoping after wrong password ([#23382](https://github.com/RocketChat/Rocket.Chat/pull/23382)) @@ -1767,7 +2991,7 @@ - resumeToken not working ([#23379](https://github.com/RocketChat/Rocket.Chat/pull/23379)) -- unwanted toastr error message when deleting user ([#23372](https://github.com/RocketChat/Rocket.Chat/pull/23372)) +- unwanted toastr error message when deleting user ([#23372](https://github.com/RocketChat/Rocket.Chat/pull/23372) by [@ostjen](https://github.com/ostjen)) - Users' `roles` and `type` being reset to default on LDAP DataSync ([#23378](https://github.com/RocketChat/Rocket.Chat/pull/23378)) @@ -1779,19 +3003,19 @@ - Chore: Update Apps-Engine version ([#23375](https://github.com/RocketChat/Rocket.Chat/pull/23375)) -- Release 4.0.1 ([#23386](https://github.com/RocketChat/Rocket.Chat/pull/23386) by [@wolbernd](https://github.com/wolbernd)) +- Release 4.0.1 ([#23386](https://github.com/RocketChat/Rocket.Chat/pull/23386) by [@ostjen](https://github.com/ostjen) & [@wolbernd](https://github.com/wolbernd))
### 👩‍💻👨‍💻 Contributors 😍 +- [@ostjen](https://github.com/ostjen) - [@wolbernd](https://github.com/wolbernd) ### 👩‍💻👨‍💻 Core Team 🤓 - [@d-gubert](https://github.com/d-gubert) - [@matheusbsilva137](https://github.com/matheusbsilva137) -- [@ostjen](https://github.com/ostjen) - [@rodrigok](https://github.com/rodrigok) - [@sampaiodiego](https://github.com/sampaiodiego) - [@tassoevan](https://github.com/tassoevan) @@ -1826,13 +3050,13 @@ - LDAP Refactoring ([#23171](https://github.com/RocketChat/Rocket.Chat/pull/23171)) -- Moved advanced oAuth features to EE ([#23201](https://github.com/RocketChat/Rocket.Chat/pull/23201)) +- Moved advanced oAuth features to EE ([#23201](https://github.com/RocketChat/Rocket.Chat/pull/23201) by [@ostjen](https://github.com/ostjen)) -- Moved role-sync and advanced SAML settings to EE ([#23107](https://github.com/RocketChat/Rocket.Chat/pull/23107)) +- Moved role-sync and advanced SAML settings to EE ([#23107](https://github.com/RocketChat/Rocket.Chat/pull/23107) by [@ostjen](https://github.com/ostjen)) -- Moved SAML custom field map to EE ([#23319](https://github.com/RocketChat/Rocket.Chat/pull/23319)) +- Moved SAML custom field map to EE ([#23319](https://github.com/RocketChat/Rocket.Chat/pull/23319) by [@ostjen](https://github.com/ostjen)) -- Remove cordova compatibility setting ([#23302](https://github.com/RocketChat/Rocket.Chat/pull/23302)) +- Remove cordova compatibility setting ([#23302](https://github.com/RocketChat/Rocket.Chat/pull/23302) by [@ostjen](https://github.com/ostjen)) - Remove deprecated endpoints ([#23162](https://github.com/RocketChat/Rocket.Chat/pull/23162)) @@ -1859,9 +3083,9 @@ This aims to clean up the code, since upgrades jumping 2 major versions are too risky and hard to maintain, we'll keep only migration from that last major (in this case 3.x). -- Remove patch info from endpoint /api/info for non-logged in users ([#16050](https://github.com/RocketChat/Rocket.Chat/pull/16050) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove patch info from endpoint /api/info for non-logged in users ([#16050](https://github.com/RocketChat/Rocket.Chat/pull/16050)) -- Removed support of MongoDB 3.4; Deprecated MongoDB 3.6 and 4.0 ([#22907](https://github.com/RocketChat/Rocket.Chat/pull/22907)) +- Removed support of MongoDB 3.4; Deprecated MongoDB 3.6 and 4.0 ([#22907](https://github.com/RocketChat/Rocket.Chat/pull/22907) by [@ostjen](https://github.com/ostjen)) - Stop sending audio notifications via stream ([#23108](https://github.com/RocketChat/Rocket.Chat/pull/23108)) @@ -1959,7 +3183,7 @@ - Use `encodeURIComponent()` to encode values received by `_generateQueryFromParams()`. -- "Read Only" and "Allow Reacting" system messages are missing in rooms ([#23037](https://github.com/RocketChat/Rocket.Chat/pull/23037)) +- "Read Only" and "Allow Reacting" system messages are missing in rooms ([#23037](https://github.com/RocketChat/Rocket.Chat/pull/23037) by [@ostjen](https://github.com/ostjen)) - Add system message to notify changes on the **"Read Only"** setting; - Add system message to notify changes on the **"Allow Reacting"** setting; @@ -1976,7 +3200,7 @@ - Check which fields have been updated before throwing errors in `validateUserEditing`. -- Inaccurate use of 'Mobile notifications' instead of 'Push notifications' in i18n strings ([#22978](https://github.com/RocketChat/Rocket.Chat/pull/22978)) +- Inaccurate use of 'Mobile notifications' instead of 'Push notifications' in i18n strings ([#22978](https://github.com/RocketChat/Rocket.Chat/pull/22978) by [@ostjen](https://github.com/ostjen)) - Fix inaccurate use of 'Mobile notifications' (which is misleading in German) by 'Push notifications'; - Update `'Notification_Mobile_Default_For'` key to `'Notification_Push_Default_For'` (and text to 'Send Push Notifications For' for English Language); @@ -2035,7 +3259,7 @@ - Update bugsnag package ([#23104](https://github.com/RocketChat/Rocket.Chat/pull/23104)) -- User list not being updated after creation/deletion of user ([#23032](https://github.com/RocketChat/Rocket.Chat/pull/23032)) +- User list not being updated after creation/deletion of user ([#23032](https://github.com/RocketChat/Rocket.Chat/pull/23032) by [@ostjen](https://github.com/ostjen)) - Wrap canned-responses endpoints with ee license ([#23204](https://github.com/RocketChat/Rocket.Chat/pull/23204)) @@ -2178,7 +3402,7 @@ Spotted by @gabriellsh. -- Regression: Removed exclusive tests statement ([#23333](https://github.com/RocketChat/Rocket.Chat/pull/23333)) +- Regression: Removed exclusive tests statement ([#23333](https://github.com/RocketChat/Rocket.Chat/pull/23333) by [@ostjen](https://github.com/ostjen)) - Regression: Request seats link ([#23312](https://github.com/RocketChat/Rocket.Chat/pull/23312)) @@ -2202,17 +3426,18 @@ ### 👩‍💻👨‍💻 Contributors 😍 -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@cuonghuunguyen](https://github.com/cuonghuunguyen) - [@dependabot[bot]](https://github.com/dependabot[bot]) - [@g-thome](https://github.com/g-thome) - [@gabrieloliverio](https://github.com/gabrieloliverio) - [@lucassartor](https://github.com/lucassartor) +- [@ostjen](https://github.com/ostjen) - [@sumukhah](https://github.com/sumukhah) ### 👩‍💻👨‍💻 Core Team 🤓 - [@KevLehman](https://github.com/KevLehman) +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@MartinSchoeler](https://github.com/MartinSchoeler) - [@casalsgh](https://github.com/casalsgh) - [@d-gubert](https://github.com/d-gubert) @@ -2223,7 +3448,6 @@ - [@graywolf336](https://github.com/graywolf336) - [@matheusbsilva137](https://github.com/matheusbsilva137) - [@murtaza98](https://github.com/murtaza98) -- [@ostjen](https://github.com/ostjen) - [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) - [@renatobecker](https://github.com/renatobecker) - [@rodrigok](https://github.com/rodrigok) @@ -2389,15 +3613,15 @@ - Bad words falling if message is empty ([#22930](https://github.com/RocketChat/Rocket.Chat/pull/22930)) -- Broken download link on uploaded files ([#22848](https://github.com/RocketChat/Rocket.Chat/pull/22848)) +- Broken download link on uploaded files ([#22848](https://github.com/RocketChat/Rocket.Chat/pull/22848) by [@ostjen](https://github.com/ostjen)) Uploaded files had wrong download links when the deploy had a sub directory. This misbehavior was caused by the wrong usage of the rtrim method, the 2nd parameter is a list of chars, [not a string](https://www.php.net/manual/pt_BR/function.rtrim.php) (this method was inspired by php) -- Can't access other administration menus after opening Engagement Dashboard ([#22870](https://github.com/RocketChat/Rocket.Chat/pull/22870)) +- Can't access other administration menus after opening Engagement Dashboard ([#22870](https://github.com/RocketChat/Rocket.Chat/pull/22870) by [@ostjen](https://github.com/ostjen)) -- Go command duplicating subfolder path on iframes. ([#22796](https://github.com/RocketChat/Rocket.Chat/pull/22796)) +- Go command duplicating subfolder path on iframes. ([#22796](https://github.com/RocketChat/Rocket.Chat/pull/22796) by [@ostjen](https://github.com/ostjen)) -- Manually approve new users is not applied to SAML users ([#22823](https://github.com/RocketChat/Rocket.Chat/pull/22823)) +- Manually approve new users is not applied to SAML users ([#22823](https://github.com/RocketChat/Rocket.Chat/pull/22823) by [@ostjen](https://github.com/ostjen)) - Production-environment dependencies ([#22868](https://github.com/RocketChat/Rocket.Chat/pull/22868)) @@ -2405,7 +3629,7 @@ - QuickActions for mobile screen ([#23016](https://github.com/RocketChat/Rocket.Chat/pull/23016)) -- Registration not possible with TOTP and email verification ([#22778](https://github.com/RocketChat/Rocket.Chat/pull/22778)) +- Registration not possible with TOTP and email verification ([#22778](https://github.com/RocketChat/Rocket.Chat/pull/22778) by [@ostjen](https://github.com/ostjen)) - Return transcript/dashboards based on timezone settings ([#22850](https://github.com/RocketChat/Rocket.Chat/pull/22850)) @@ -2430,7 +3654,7 @@ - TypeError on Callout type prop ([#22790](https://github.com/RocketChat/Rocket.Chat/pull/22790) by [@hrahul2605](https://github.com/hrahul2605)) -- User is still asked for 2FA confirmation even if it is deactivated ([#22801](https://github.com/RocketChat/Rocket.Chat/pull/22801)) +- User is still asked for 2FA confirmation even if it is deactivated ([#22801](https://github.com/RocketChat/Rocket.Chat/pull/22801) by [@ostjen](https://github.com/ostjen)) - User presence being processes even if presence monitor was disabled ([#22927](https://github.com/RocketChat/Rocket.Chat/pull/22927)) @@ -2509,6 +3733,7 @@ - [@hrahul2605](https://github.com/hrahul2605) - [@jsm84](https://github.com/jsm84) - [@nmagedman](https://github.com/nmagedman) +- [@ostjen](https://github.com/ostjen) - [@piotrkochan](https://github.com/piotrkochan) ### 👩‍💻👨‍💻 Core Team 🤓 @@ -2522,7 +3747,6 @@ - [@marceloschmidt](https://github.com/marceloschmidt) - [@matheusbsilva137](https://github.com/matheusbsilva137) - [@murtaza98](https://github.com/murtaza98) -- [@ostjen](https://github.com/ostjen) - [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) - [@renatobecker](https://github.com/renatobecker) - [@sampaiodiego](https://github.com/sampaiodiego) @@ -2640,14 +3864,14 @@ - Federation setup ([#22208](https://github.com/RocketChat/Rocket.Chat/pull/22208) by [@g-thome](https://github.com/g-thome)) -- Logout other user endpoint ([#22661](https://github.com/RocketChat/Rocket.Chat/pull/22661)) +- Logout other user endpoint ([#22661](https://github.com/RocketChat/Rocket.Chat/pull/22661) by [@ostjen](https://github.com/ostjen)) - Monitoring Track messages' round trip time ([#22676](https://github.com/RocketChat/Rocket.Chat/pull/22676)) Track messages' roundtrip time from backend saves time to the time when received back from the oplog allowing track of oplog slowness. Prometheus metric: `rocketchat_messages_roundtrip_time` -- REST endpoint to remove User from Role ([#20485](https://github.com/RocketChat/Rocket.Chat/pull/20485) by [@Cosnavel](https://github.com/Cosnavel) & [@lucassartor](https://github.com/lucassartor)) +- REST endpoint to remove User from Role ([#20485](https://github.com/RocketChat/Rocket.Chat/pull/20485) by [@Cosnavel](https://github.com/Cosnavel) & [@lucassartor](https://github.com/lucassartor) & [@ostjen](https://github.com/ostjen)) ### 🚀 Improvements @@ -2768,7 +3992,7 @@ If the commit hash happens to be null, the administration page will still attempt to slice the value and display it. This causes the admin page to not display, and essentially crash the web app. This fixes it by checking for a null value first. -- Blank screen in message auditing DM tab ([#22763](https://github.com/RocketChat/Rocket.Chat/pull/22763)) +- Blank screen in message auditing DM tab ([#22763](https://github.com/RocketChat/Rocket.Chat/pull/22763) by [@ostjen](https://github.com/ostjen)) The DM tab in message auditing was displaying a blank screen, instead of the actual tab. @@ -2805,9 +4029,9 @@ **New behavior:** ![image](https://user-images.githubusercontent.com/49413772/124958882-05a8e800-dff1-11eb-8203-b34a4f1c98a0.png) -- Channel is automatically getting added to the first option in move to team feature ([#22670](https://github.com/RocketChat/Rocket.Chat/pull/22670)) +- Channel is automatically getting added to the first option in move to team feature ([#22670](https://github.com/RocketChat/Rocket.Chat/pull/22670) by [@ostjen](https://github.com/ostjen)) -- Channels or Teams deleted are not removed from the sidebar. ([#22613](https://github.com/RocketChat/Rocket.Chat/pull/22613)) +- Channels or Teams deleted are not removed from the sidebar. ([#22613](https://github.com/RocketChat/Rocket.Chat/pull/22613) by [@ostjen](https://github.com/ostjen)) - Checks the list of agents if at least one is online ([#22584](https://github.com/RocketChat/Rocket.Chat/pull/22584)) @@ -2815,7 +4039,7 @@ - Content-Security-Policy ignoring CDN configuration ([#22791](https://github.com/RocketChat/Rocket.Chat/pull/22791) by [@nmagedman](https://github.com/nmagedman)) -- Create discussion modal - cancel button and invite users alignment ([#22718](https://github.com/RocketChat/Rocket.Chat/pull/22718)) +- Create discussion modal - cancel button and invite users alignment ([#22718](https://github.com/RocketChat/Rocket.Chat/pull/22718) by [@ostjen](https://github.com/ostjen)) Changes in "open discussion" modal @@ -2918,11 +4142,11 @@ - Chore: [Snyk] Security upgrade node-gcm from 0.14.4 to 1.0.0 ([#22582](https://github.com/RocketChat/Rocket.Chat/pull/22582) by [@snyk-bot](https://github.com/snyk-bot)) -- Chore: added pagination to search msg endpoint ([#22632](https://github.com/RocketChat/Rocket.Chat/pull/22632)) +- Chore: added pagination to search msg endpoint ([#22632](https://github.com/RocketChat/Rocket.Chat/pull/22632) by [@ostjen](https://github.com/ostjen)) - Chore: Create README.md ([#22615](https://github.com/RocketChat/Rocket.Chat/pull/22615)) -- Chore: Enable Omnicahnnel by default ([#22697](https://github.com/RocketChat/Rocket.Chat/pull/22697)) +- Chore: Enable Omnicahnnel by default ([#22697](https://github.com/RocketChat/Rocket.Chat/pull/22697) by [@ostjen](https://github.com/ostjen)) - Chore: Meteor 2.2 and bump dependencies ([#22399](https://github.com/RocketChat/Rocket.Chat/pull/22399)) @@ -3058,6 +4282,7 @@ - [@g-thome](https://github.com/g-thome) - [@lucassartor](https://github.com/lucassartor) - [@nmagedman](https://github.com/nmagedman) +- [@ostjen](https://github.com/ostjen) - [@rafaelblink](https://github.com/rafaelblink) - [@snyk-bot](https://github.com/snyk-bot) @@ -3073,7 +4298,6 @@ - [@ggazzo](https://github.com/ggazzo) - [@matheusbsilva137](https://github.com/matheusbsilva137) - [@murtaza98](https://github.com/murtaza98) -- [@ostjen](https://github.com/ostjen) - [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) - [@renatobecker](https://github.com/renatobecker) - [@rodrigok](https://github.com/rodrigok) @@ -7358,7 +8582,7 @@ ### 🎉 New features -- 2 Factor Authentication when using OAuth and SAML ([#11726](https://github.com/RocketChat/Rocket.Chat/pull/11726) by [@Hudell](https://github.com/Hudell) & [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- 2 Factor Authentication when using OAuth and SAML ([#11726](https://github.com/RocketChat/Rocket.Chat/pull/11726) by [@Hudell](https://github.com/Hudell)) - Added setting to disable password changes for users who log in using SSO ([#10391](https://github.com/RocketChat/Rocket.Chat/pull/10391) by [@Hudell](https://github.com/Hudell)) @@ -7537,7 +8761,6 @@ ### 👩‍💻👨‍💻 Contributors 😍 - [@Hudell](https://github.com/Hudell) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@antkaz](https://github.com/antkaz) - [@dependabot[bot]](https://github.com/dependabot[bot]) - [@g-thome](https://github.com/g-thome) @@ -7549,6 +8772,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@MartinSchoeler](https://github.com/MartinSchoeler) - [@d-gubert](https://github.com/d-gubert) - [@dougfabris](https://github.com/dougfabris) @@ -8483,7 +9707,7 @@ - Agents enabledDepartment attribute not set on collection ([#18614](https://github.com/RocketChat/Rocket.Chat/pull/18614) by [@paulobernardoaf](https://github.com/paulobernardoaf)) -- Anonymous users were created as inactive if the manual approval setting was enabled ([#17427](https://github.com/RocketChat/Rocket.Chat/pull/17427) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Anonymous users were created as inactive if the manual approval setting was enabled ([#17427](https://github.com/RocketChat/Rocket.Chat/pull/17427)) - Auto complete user suggestions ([#18437](https://github.com/RocketChat/Rocket.Chat/pull/18437)) @@ -8621,7 +9845,6 @@ ### 👩‍💻👨‍💻 Contributors 😍 -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@antkaz](https://github.com/antkaz) - [@densik](https://github.com/densik) - [@dependabot[bot]](https://github.com/dependabot[bot]) @@ -8634,6 +9857,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@MartinSchoeler](https://github.com/MartinSchoeler) - [@Sing-Li](https://github.com/Sing-Li) - [@d-gubert](https://github.com/d-gubert) @@ -8822,7 +10046,7 @@ - Change setting that blocks unauthenticated access to avatar to public ([#18316](https://github.com/RocketChat/Rocket.Chat/pull/18316) by [@djorkaeffalexandre](https://github.com/djorkaeffalexandre)) -- Improve performance and remove agents when the department is removed ([#17049](https://github.com/RocketChat/Rocket.Chat/pull/17049) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Improve performance and remove agents when the department is removed ([#17049](https://github.com/RocketChat/Rocket.Chat/pull/17049)) - List dropdown ([#18081](https://github.com/RocketChat/Rocket.Chat/pull/18081)) @@ -8912,7 +10136,7 @@ - LingoHub based on develop ([#18176](https://github.com/RocketChat/Rocket.Chat/pull/18176)) -- Merge master into develop & Set version to 3.5.0-develop ([#18083](https://github.com/RocketChat/Rocket.Chat/pull/18083) by [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@cking-vonix](https://github.com/cking-vonix) & [@lpilz](https://github.com/lpilz) & [@mariaeduardacunha](https://github.com/mariaeduardacunha)) +- Merge master into develop & Set version to 3.5.0-develop ([#18083](https://github.com/RocketChat/Rocket.Chat/pull/18083) by [@cking-vonix](https://github.com/cking-vonix) & [@lpilz](https://github.com/lpilz) & [@mariaeduardacunha](https://github.com/mariaeduardacunha)) - Move the development guidelines to our handbook ([#18026](https://github.com/RocketChat/Rocket.Chat/pull/18026)) @@ -8979,7 +10203,6 @@ ### 👩‍💻👨‍💻 Contributors 😍 - [@20051231](https://github.com/20051231) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@antkaz](https://github.com/antkaz) - [@cking-vonix](https://github.com/cking-vonix) - [@darigovresearch](https://github.com/darigovresearch) @@ -8991,6 +10214,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@MartinSchoeler](https://github.com/MartinSchoeler) - [@d-gubert](https://github.com/d-gubert) - [@gabriellsh](https://github.com/gabriellsh) @@ -9115,13 +10339,13 @@ - **ENTERPRISE:** Download engagement data ([#17920](https://github.com/RocketChat/Rocket.Chat/pull/17920)) -- **ENTERPRISE:** Omnichannel multiple business hours ([#17947](https://github.com/RocketChat/Rocket.Chat/pull/17947) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- **ENTERPRISE:** Omnichannel multiple business hours ([#17947](https://github.com/RocketChat/Rocket.Chat/pull/17947)) - Ability to configure Jitsi room options via new setting `URL Suffix` ([#17950](https://github.com/RocketChat/Rocket.Chat/pull/17950) by [@fthiery](https://github.com/fthiery)) - Accept variable `#{userdn}` on LDAP group filter ([#16273](https://github.com/RocketChat/Rocket.Chat/pull/16273) by [@ChrissW-R1](https://github.com/ChrissW-R1)) -- Add ability to block failed login attempts by user and IP ([#17783](https://github.com/RocketChat/Rocket.Chat/pull/17783) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add ability to block failed login attempts by user and IP ([#17783](https://github.com/RocketChat/Rocket.Chat/pull/17783)) - Allows agents to send chat transcript to omnichannel end-users ([#17774](https://github.com/RocketChat/Rocket.Chat/pull/17774)) @@ -9162,7 +10386,7 @@ - React hooks lint rules ([#17941](https://github.com/RocketChat/Rocket.Chat/pull/17941)) -- Refactor Omnichannel Office Hours feature ([#17824](https://github.com/RocketChat/Rocket.Chat/pull/17824) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Refactor Omnichannel Office Hours feature ([#17824](https://github.com/RocketChat/Rocket.Chat/pull/17824)) - Refactor Omnichannel Past Chats List ([#17346](https://github.com/RocketChat/Rocket.Chat/pull/17346) by [@nitinkumartiwari](https://github.com/nitinkumartiwari)) @@ -9353,7 +10577,7 @@ - Regression: Image Upload not working ([#17993](https://github.com/RocketChat/Rocket.Chat/pull/17993)) -- Regression: Improve Omnichannel Business Hours ([#18050](https://github.com/RocketChat/Rocket.Chat/pull/18050) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Regression: Improve Omnichannel Business Hours ([#18050](https://github.com/RocketChat/Rocket.Chat/pull/18050)) - Regression: Improve the logic to get request IPs ([#18033](https://github.com/RocketChat/Rocket.Chat/pull/18033)) @@ -9399,7 +10623,6 @@ - [@EwoutH](https://github.com/EwoutH) - [@InstinctBas](https://github.com/InstinctBas) - [@Karting06](https://github.com/Karting06) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@Siedlerchr](https://github.com/Siedlerchr) - [@alexbartsch](https://github.com/alexbartsch) - [@antkaz](https://github.com/antkaz) @@ -9433,6 +10656,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@MartinSchoeler](https://github.com/MartinSchoeler) - [@Sing-Li](https://github.com/Sing-Li) - [@alansikora](https://github.com/alansikora) @@ -9532,11 +10756,11 @@ 🔍 Minor changes -- [REGRESSION] Omnichannel visitor forward was applying wrong restrictions ([#17826](https://github.com/RocketChat/Rocket.Chat/pull/17826) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- [REGRESSION] Omnichannel visitor forward was applying wrong restrictions ([#17826](https://github.com/RocketChat/Rocket.Chat/pull/17826)) - Fix the update check not working ([#17809](https://github.com/RocketChat/Rocket.Chat/pull/17809)) -- Release 3.3.1 ([#17865](https://github.com/RocketChat/Rocket.Chat/pull/17865) by [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@cking-vonix](https://github.com/cking-vonix) & [@lpilz](https://github.com/lpilz) & [@mariaeduardacunha](https://github.com/mariaeduardacunha)) +- Release 3.3.1 ([#17865](https://github.com/RocketChat/Rocket.Chat/pull/17865) by [@cking-vonix](https://github.com/cking-vonix) & [@lpilz](https://github.com/lpilz) & [@mariaeduardacunha](https://github.com/mariaeduardacunha)) - Update Apps-Engine version ([#17804](https://github.com/RocketChat/Rocket.Chat/pull/17804)) @@ -9546,13 +10770,13 @@ ### 👩‍💻👨‍💻 Contributors 😍 -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@cking-vonix](https://github.com/cking-vonix) - [@lpilz](https://github.com/lpilz) - [@mariaeduardacunha](https://github.com/mariaeduardacunha) ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@d-gubert](https://github.com/d-gubert) - [@geekgonecrazy](https://github.com/geekgonecrazy) - [@graywolf336](https://github.com/graywolf336) @@ -9590,11 +10814,11 @@ - **ENTERPRISE:** Support Omnichannel conversations auditing ([#17692](https://github.com/RocketChat/Rocket.Chat/pull/17692)) -- Add Livechat website URL to the offline message e-mail ([#17429](https://github.com/RocketChat/Rocket.Chat/pull/17429) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add Livechat website URL to the offline message e-mail ([#17429](https://github.com/RocketChat/Rocket.Chat/pull/17429)) -- Add permissions to deal with Omnichannel custom fields ([#17567](https://github.com/RocketChat/Rocket.Chat/pull/17567) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add permissions to deal with Omnichannel custom fields ([#17567](https://github.com/RocketChat/Rocket.Chat/pull/17567)) -- Add Permissions to deal with Omnichannel visitor past chats history ([#17580](https://github.com/RocketChat/Rocket.Chat/pull/17580) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add Permissions to deal with Omnichannel visitor past chats history ([#17580](https://github.com/RocketChat/Rocket.Chat/pull/17580)) - Add the ability to send Livechat offline messages to a channel ([#17442](https://github.com/RocketChat/Rocket.Chat/pull/17442)) @@ -9604,7 +10828,7 @@ - Admin refactor Second phase ([#17551](https://github.com/RocketChat/Rocket.Chat/pull/17551)) -- Allow filtering Omnichannel analytics dashboards by department ([#17463](https://github.com/RocketChat/Rocket.Chat/pull/17463) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Allow filtering Omnichannel analytics dashboards by department ([#17463](https://github.com/RocketChat/Rocket.Chat/pull/17463)) - API endpoint to fetch Omnichannel's room transfer history ([#17694](https://github.com/RocketChat/Rocket.Chat/pull/17694)) @@ -9695,7 +10919,7 @@ - Omnichannel departments are not saved when the offline channel name is not defined ([#17553](https://github.com/RocketChat/Rocket.Chat/pull/17553)) -- Omnichannel room priorities system messages were create on every saved room info ([#17479](https://github.com/RocketChat/Rocket.Chat/pull/17479) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Omnichannel room priorities system messages were create on every saved room info ([#17479](https://github.com/RocketChat/Rocket.Chat/pull/17479)) - Password reset/change accepting current password as new password ([#16331](https://github.com/RocketChat/Rocket.Chat/pull/16331) by [@ashwaniYDV](https://github.com/ashwaniYDV)) @@ -9715,7 +10939,7 @@ - remove multiple options from dontAskMeAgain ([#17514](https://github.com/RocketChat/Rocket.Chat/pull/17514) by [@TaimurAzhar](https://github.com/TaimurAzhar)) -- Replace obsolete X-FRAME-OPTIONS header on Livechat route ([#17419](https://github.com/RocketChat/Rocket.Chat/pull/17419) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace obsolete X-FRAME-OPTIONS header on Livechat route ([#17419](https://github.com/RocketChat/Rocket.Chat/pull/17419)) - Replace postcss Meteor package ([#15929](https://github.com/RocketChat/Rocket.Chat/pull/15929)) @@ -9751,7 +10975,7 @@ - Improve: New PR Template ([#16968](https://github.com/RocketChat/Rocket.Chat/pull/16968) by [@regalstreak](https://github.com/regalstreak)) -- Improve: Remove index files from action-links, accounts and assets ([#17607](https://github.com/RocketChat/Rocket.Chat/pull/17607) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Improve: Remove index files from action-links, accounts and assets ([#17607](https://github.com/RocketChat/Rocket.Chat/pull/17607)) - Improve: Remove uncessary RegExp query by email ([#17654](https://github.com/RocketChat/Rocket.Chat/pull/17654)) @@ -9829,7 +11053,6 @@ ### 👩‍💻👨‍💻 Contributors 😍 -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@Nikhil713](https://github.com/Nikhil713) - [@TaimurAzhar](https://github.com/TaimurAzhar) - [@ashwaniYDV](https://github.com/ashwaniYDV) @@ -9857,6 +11080,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@MartinSchoeler](https://github.com/MartinSchoeler) - [@d-gubert](https://github.com/d-gubert) - [@engelgabriel](https://github.com/engelgabriel) @@ -9954,11 +11178,11 @@ ### 🎉 New features -- **ENTERPRISE:** Allows to set a group of departments accepted for forwarding chats ([#17335](https://github.com/RocketChat/Rocket.Chat/pull/17335) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- **ENTERPRISE:** Allows to set a group of departments accepted for forwarding chats ([#17335](https://github.com/RocketChat/Rocket.Chat/pull/17335)) -- **ENTERPRISE:** Auto close abandoned Omnichannel rooms ([#17055](https://github.com/RocketChat/Rocket.Chat/pull/17055) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- **ENTERPRISE:** Auto close abandoned Omnichannel rooms ([#17055](https://github.com/RocketChat/Rocket.Chat/pull/17055)) -- **ENTERPRISE:** Omnichannel queue priorities ([#17141](https://github.com/RocketChat/Rocket.Chat/pull/17141) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- **ENTERPRISE:** Omnichannel queue priorities ([#17141](https://github.com/RocketChat/Rocket.Chat/pull/17141)) - **ENTERPRISE:** Restrict the permissions configuration for guest users ([#17333](https://github.com/RocketChat/Rocket.Chat/pull/17333)) @@ -10009,7 +11233,7 @@ - Add `file-title` and `file-desc` as new filter tag options on message search ([#16858](https://github.com/RocketChat/Rocket.Chat/pull/16858) by [@subham103](https://github.com/subham103)) -- Add possibility to sort the Omnichannel current chats list by column ([#17347](https://github.com/RocketChat/Rocket.Chat/pull/17347) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add possibility to sort the Omnichannel current chats list by column ([#17347](https://github.com/RocketChat/Rocket.Chat/pull/17347)) - Administration -> Mailer Rewrite. ([#17191](https://github.com/RocketChat/Rocket.Chat/pull/17191)) @@ -10074,7 +11298,7 @@ - Red color error outline is not removed after password update on profile details ([#16536](https://github.com/RocketChat/Rocket.Chat/pull/16536) by [@ashwaniYDV](https://github.com/ashwaniYDV)) -- Remove properties from users.info response ([#17238](https://github.com/RocketChat/Rocket.Chat/pull/17238) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove properties from users.info response ([#17238](https://github.com/RocketChat/Rocket.Chat/pull/17238)) - SAML assertion signature enforcement ([#17278](https://github.com/RocketChat/Rocket.Chat/pull/17278)) @@ -10147,7 +11371,6 @@ - [@1rV1N-git](https://github.com/1rV1N-git) - [@CC007](https://github.com/CC007) - [@Krinkle](https://github.com/Krinkle) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@Nikhil713](https://github.com/Nikhil713) - [@RavenSystem](https://github.com/RavenSystem) - [@aKn1ghtOut](https://github.com/aKn1ghtOut) @@ -10170,6 +11393,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@MartinSchoeler](https://github.com/MartinSchoeler) - [@alansikora](https://github.com/alansikora) - [@d-gubert](https://github.com/d-gubert) @@ -10304,7 +11528,7 @@ ### 🎉 New features -- **ENTERPRISE:** Engagement Dashboard ([#16960](https://github.com/RocketChat/Rocket.Chat/pull/16960) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- **ENTERPRISE:** Engagement Dashboard ([#16960](https://github.com/RocketChat/Rocket.Chat/pull/16960)) - Add default chat closing tags in Omnichannel departments ([#16859](https://github.com/RocketChat/Rocket.Chat/pull/16859)) @@ -10334,7 +11558,7 @@ - Open the Visitor Info panel automatically when the agent enters an Omnichannel room ([#16496](https://github.com/RocketChat/Rocket.Chat/pull/16496)) -- Route to get updated roles after a date ([#16610](https://github.com/RocketChat/Rocket.Chat/pull/16610) by [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@ashwaniYDV](https://github.com/ashwaniYDV)) +- Route to get updated roles after a date ([#16610](https://github.com/RocketChat/Rocket.Chat/pull/16610) by [@ashwaniYDV](https://github.com/ashwaniYDV)) - SAML config to allow clock drift ([#16751](https://github.com/RocketChat/Rocket.Chat/pull/16751) by [@localguru](https://github.com/localguru)) @@ -10358,11 +11582,11 @@ ### 🚀 Improvements -- Ability to change offline message button link on emails notifications ([#16784](https://github.com/RocketChat/Rocket.Chat/pull/16784) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Ability to change offline message button link on emails notifications ([#16784](https://github.com/RocketChat/Rocket.Chat/pull/16784)) - Accept open formarts of text, spreadsheet, presentation for upload by default ([#16502](https://github.com/RocketChat/Rocket.Chat/pull/16502)) -- Add option to require authentication on user's shield endpoint ([#16845](https://github.com/RocketChat/Rocket.Chat/pull/16845) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add option to require authentication on user's shield endpoint ([#16845](https://github.com/RocketChat/Rocket.Chat/pull/16845)) - Added autofocus to Directory ([#16217](https://github.com/RocketChat/Rocket.Chat/pull/16217) by [@ashwaniYDV](https://github.com/ashwaniYDV)) @@ -10380,11 +11604,11 @@ - Fallback content-type as application/octet-stream for FileSystem uploads ([#16776](https://github.com/RocketChat/Rocket.Chat/pull/16776) by [@georgmu](https://github.com/georgmu)) -- First data load from existing data on engagement dashboard ([#17035](https://github.com/RocketChat/Rocket.Chat/pull/17035) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- First data load from existing data on engagement dashboard ([#17035](https://github.com/RocketChat/Rocket.Chat/pull/17035)) - Increase the push throughput to prevent queuing ([#17194](https://github.com/RocketChat/Rocket.Chat/pull/17194)) -- Omnichannel aggregations performance improvements ([#16755](https://github.com/RocketChat/Rocket.Chat/pull/16755) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Omnichannel aggregations performance improvements ([#16755](https://github.com/RocketChat/Rocket.Chat/pull/16755)) - Removed the 'reply in thread' from thread replies ([#16630](https://github.com/RocketChat/Rocket.Chat/pull/16630) by [@ritwizsinha](https://github.com/ritwizsinha)) @@ -10407,7 +11631,7 @@ - "Jump to message" is rendered twice when message is starred. ([#16170](https://github.com/RocketChat/Rocket.Chat/pull/16170) by [@ashwaniYDV](https://github.com/ashwaniYDV)) -- `users.setStatus` API was ignoring the user from params when trying to set status of other users ([#16128](https://github.com/RocketChat/Rocket.Chat/pull/16128) by [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@rm-yakovenko](https://github.com/rm-yakovenko)) +- `users.setStatus` API was ignoring the user from params when trying to set status of other users ([#16128](https://github.com/RocketChat/Rocket.Chat/pull/16128) by [@rm-yakovenko](https://github.com/rm-yakovenko)) - Additional scroll when contextual bar is open ([#16667](https://github.com/RocketChat/Rocket.Chat/pull/16667)) @@ -10473,7 +11697,7 @@ - LDAP sync admin action was not syncing existent users ([#16671](https://github.com/RocketChat/Rocket.Chat/pull/16671)) -- livechat/rooms endpoint not working with big amount of livechats ([#16623](https://github.com/RocketChat/Rocket.Chat/pull/16623) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- livechat/rooms endpoint not working with big amount of livechats ([#16623](https://github.com/RocketChat/Rocket.Chat/pull/16623)) - Login with LinkedIn not mapping name and picture correctly ([#16955](https://github.com/RocketChat/Rocket.Chat/pull/16955)) @@ -10535,7 +11759,7 @@ - UiKit not updating new actionIds received as responses from actions ([#16624](https://github.com/RocketChat/Rocket.Chat/pull/16624)) -- users.info endpoint not handling the error if the user does not exist ([#16495](https://github.com/RocketChat/Rocket.Chat/pull/16495) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- users.info endpoint not handling the error if the user does not exist ([#16495](https://github.com/RocketChat/Rocket.Chat/pull/16495)) - Verification email body ([#17062](https://github.com/RocketChat/Rocket.Chat/pull/17062) by [@GOVINDDIXIT](https://github.com/GOVINDDIXIT)) @@ -10569,7 +11793,7 @@ - Add lint to `.less` files ([#16893](https://github.com/RocketChat/Rocket.Chat/pull/16893)) -- Add methods to include room types on dashboard ([#16576](https://github.com/RocketChat/Rocket.Chat/pull/16576) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add methods to include room types on dashboard ([#16576](https://github.com/RocketChat/Rocket.Chat/pull/16576)) - Add new Omnichannel department forwarding callback ([#16779](https://github.com/RocketChat/Rocket.Chat/pull/16779)) @@ -10631,7 +11855,7 @@ - Improve room types usage ([#16753](https://github.com/RocketChat/Rocket.Chat/pull/16753)) -- Improve: Apps-engine E2E tests ([#16781](https://github.com/RocketChat/Rocket.Chat/pull/16781) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Improve: Apps-engine E2E tests ([#16781](https://github.com/RocketChat/Rocket.Chat/pull/16781)) - LingoHub based on develop ([#16837](https://github.com/RocketChat/Rocket.Chat/pull/16837)) @@ -10649,7 +11873,7 @@ - Reduce notifyUser propagation ([#17088](https://github.com/RocketChat/Rocket.Chat/pull/17088)) -- Regression: `users.setStatus` throwing an error if message is empty ([#17036](https://github.com/RocketChat/Rocket.Chat/pull/17036) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Regression: `users.setStatus` throwing an error if message is empty ([#17036](https://github.com/RocketChat/Rocket.Chat/pull/17036)) - Regression: Admin create user button ([#17186](https://github.com/RocketChat/Rocket.Chat/pull/17186)) @@ -10693,7 +11917,7 @@ - Regression: Invite links working for group DMs ([#17056](https://github.com/RocketChat/Rocket.Chat/pull/17056)) -- Regression: OmniChannel agent activity monitor was counting time wrongly ([#16979](https://github.com/RocketChat/Rocket.Chat/pull/16979) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Regression: OmniChannel agent activity monitor was counting time wrongly ([#16979](https://github.com/RocketChat/Rocket.Chat/pull/16979)) - Regression: omnichannel manual queued sidebarlist ([#17048](https://github.com/RocketChat/Rocket.Chat/pull/17048)) @@ -10731,7 +11955,6 @@ - [@1rV1N-git](https://github.com/1rV1N-git) - [@GOVINDDIXIT](https://github.com/GOVINDDIXIT) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@Nikhil713](https://github.com/Nikhil713) - [@aKn1ghtOut](https://github.com/aKn1ghtOut) - [@antkaz](https://github.com/antkaz) @@ -10759,6 +11982,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@PrajvalRaval](https://github.com/PrajvalRaval) - [@Rodriq](https://github.com/Rodriq) - [@Sing-Li](https://github.com/Sing-Li) @@ -11082,14 +12306,11 @@ - Omnichannel Inquiry queues when removing chats ([#16603](https://github.com/RocketChat/Rocket.Chat/pull/16603)) -- users.info endpoint not handling the error if the user does not exist ([#16495](https://github.com/RocketChat/Rocket.Chat/pull/16495) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) - -### 👩‍💻👨‍💻 Contributors 😍 - -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) +- users.info endpoint not handling the error if the user does not exist ([#16495](https://github.com/RocketChat/Rocket.Chat/pull/16495)) ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@gabriellsh](https://github.com/gabriellsh) - [@ggazzo](https://github.com/ggazzo) - [@renatobecker](https://github.com/renatobecker) @@ -11112,7 +12333,7 @@ - Data converters overriding fields added by apps ([#16639](https://github.com/RocketChat/Rocket.Chat/pull/16639)) -- livechat/rooms endpoint not working with big amount of livechats ([#16623](https://github.com/RocketChat/Rocket.Chat/pull/16623) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- livechat/rooms endpoint not working with big amount of livechats ([#16623](https://github.com/RocketChat/Rocket.Chat/pull/16623)) - Regression: Jitsi on external window infinite loop ([#16625](https://github.com/RocketChat/Rocket.Chat/pull/16625)) @@ -11120,12 +12341,9 @@ - UiKit not updating new actionIds received as responses from actions ([#16624](https://github.com/RocketChat/Rocket.Chat/pull/16624)) -### 👩‍💻👨‍💻 Contributors 😍 - -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@d-gubert](https://github.com/d-gubert) - [@ggazzo](https://github.com/ggazzo) - [@sampaiodiego](https://github.com/sampaiodiego) @@ -11147,7 +12365,7 @@ - Hide system messages ([#16243](https://github.com/RocketChat/Rocket.Chat/pull/16243) by [@mariaeduardacunha](https://github.com/mariaeduardacunha)) -- Remove deprecated publications ([#16351](https://github.com/RocketChat/Rocket.Chat/pull/16351) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove deprecated publications ([#16351](https://github.com/RocketChat/Rocket.Chat/pull/16351)) - Removed room counter from sidebar ([#16036](https://github.com/RocketChat/Rocket.Chat/pull/16036)) @@ -11160,7 +12378,7 @@ - Add GUI for customFields in Omnichannel conversations ([#15840](https://github.com/RocketChat/Rocket.Chat/pull/15840) by [@antkaz](https://github.com/antkaz)) -- Button to download admin server info ([#16059](https://github.com/RocketChat/Rocket.Chat/pull/16059) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Button to download admin server info ([#16059](https://github.com/RocketChat/Rocket.Chat/pull/16059)) - Check the Omnichannel service status per Department ([#16425](https://github.com/RocketChat/Rocket.Chat/pull/16425) by [@lolimay](https://github.com/lolimay)) @@ -11212,7 +12430,7 @@ - Adding 'lang' tag ([#16375](https://github.com/RocketChat/Rocket.Chat/pull/16375) by [@mariaeduardacunha](https://github.com/mariaeduardacunha)) -- api-bypass-rate-limiter permission was not working ([#16080](https://github.com/RocketChat/Rocket.Chat/pull/16080) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- api-bypass-rate-limiter permission was not working ([#16080](https://github.com/RocketChat/Rocket.Chat/pull/16080)) - App removal was moving logs to the trash collection ([#16362](https://github.com/RocketChat/Rocket.Chat/pull/16362)) @@ -11238,7 +12456,7 @@ - Integrations admin page ([#16183](https://github.com/RocketChat/Rocket.Chat/pull/16183)) -- Integrations list without pagination and outgoing integration creation ([#16233](https://github.com/RocketChat/Rocket.Chat/pull/16233) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Integrations list without pagination and outgoing integration creation ([#16233](https://github.com/RocketChat/Rocket.Chat/pull/16233)) - Introduce AppLivechatBridge.isOnlineAsync method ([#16467](https://github.com/RocketChat/Rocket.Chat/pull/16467)) @@ -11254,13 +12472,13 @@ - Missing edited icon in newly created messages ([#16484](https://github.com/RocketChat/Rocket.Chat/pull/16484)) -- Option to make a channel default ([#16433](https://github.com/RocketChat/Rocket.Chat/pull/16433) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Option to make a channel default ([#16433](https://github.com/RocketChat/Rocket.Chat/pull/16433)) - Read Message after receive a message and the room is opened ([#16473](https://github.com/RocketChat/Rocket.Chat/pull/16473)) - Readme Help wanted section ([#16197](https://github.com/RocketChat/Rocket.Chat/pull/16197)) -- Result of get avatar from url can be null ([#16123](https://github.com/RocketChat/Rocket.Chat/pull/16123) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Result of get avatar from url can be null ([#16123](https://github.com/RocketChat/Rocket.Chat/pull/16123)) - Role tags missing - Description field explanation ([#16356](https://github.com/RocketChat/Rocket.Chat/pull/16356) by [@mariaeduardacunha](https://github.com/mariaeduardacunha)) @@ -11368,7 +12586,7 @@ - Regression: Update Uikit ([#16515](https://github.com/RocketChat/Rocket.Chat/pull/16515)) -- Release 2.4.7 ([#16444](https://github.com/RocketChat/Rocket.Chat/pull/16444) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Release 2.4.7 ([#16444](https://github.com/RocketChat/Rocket.Chat/pull/16444)) - Release 2.4.9 ([#16544](https://github.com/RocketChat/Rocket.Chat/pull/16544)) @@ -11376,7 +12594,7 @@ - Revert importer streamed uploads ([#16465](https://github.com/RocketChat/Rocket.Chat/pull/16465)) -- Revert message properties validation ([#16395](https://github.com/RocketChat/Rocket.Chat/pull/16395) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Revert message properties validation ([#16395](https://github.com/RocketChat/Rocket.Chat/pull/16395)) - Send build artifacts to S3 ([#16237](https://github.com/RocketChat/Rocket.Chat/pull/16237)) @@ -11395,7 +12613,6 @@ ### 👩‍💻👨‍💻 Contributors 😍 - [@Cool-fire](https://github.com/Cool-fire) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@antkaz](https://github.com/antkaz) - [@ashwaniYDV](https://github.com/ashwaniYDV) - [@aviral243](https://github.com/aviral243) @@ -11408,6 +12625,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 - [@LuluGO](https://github.com/LuluGO) +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@MartinSchoeler](https://github.com/MartinSchoeler) - [@d-gubert](https://github.com/d-gubert) - [@gabriellsh](https://github.com/gabriellsh) @@ -11467,14 +12685,11 @@ ### 🐛 Bug fixes -- users.info endpoint not handling the error if the user does not exist ([#16495](https://github.com/RocketChat/Rocket.Chat/pull/16495) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) - -### 👩‍💻👨‍💻 Contributors 😍 - -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) +- users.info endpoint not handling the error if the user does not exist ([#16495](https://github.com/RocketChat/Rocket.Chat/pull/16495)) ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@sampaiodiego](https://github.com/sampaiodiego) # 2.4.9 @@ -11535,22 +12750,19 @@ ### 🐛 Bug fixes -- Option to make a channel default ([#16433](https://github.com/RocketChat/Rocket.Chat/pull/16433) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Option to make a channel default ([#16433](https://github.com/RocketChat/Rocket.Chat/pull/16433))
🔍 Minor changes -- Release 2.4.7 ([#16444](https://github.com/RocketChat/Rocket.Chat/pull/16444) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Release 2.4.7 ([#16444](https://github.com/RocketChat/Rocket.Chat/pull/16444))
-### 👩‍💻👨‍💻 Contributors 😍 - -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@ggazzo](https://github.com/ggazzo) # 2.4.6 @@ -11567,18 +12779,15 @@ - Fix index creation for apps_logs collection ([#16401](https://github.com/RocketChat/Rocket.Chat/pull/16401)) -- Release 2.4.6 ([#16402](https://github.com/RocketChat/Rocket.Chat/pull/16402) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Release 2.4.6 ([#16402](https://github.com/RocketChat/Rocket.Chat/pull/16402)) -- Revert message properties validation ([#16395](https://github.com/RocketChat/Rocket.Chat/pull/16395) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Revert message properties validation ([#16395](https://github.com/RocketChat/Rocket.Chat/pull/16395)) -### 👩‍💻👨‍💻 Contributors 😍 - -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@rodrigok](https://github.com/rodrigok) - [@sampaiodiego](https://github.com/sampaiodiego) @@ -11669,7 +12878,7 @@ ### 🐛 Bug fixes -- Integrations list without pagination and outgoing integration creation ([#16233](https://github.com/RocketChat/Rocket.Chat/pull/16233) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Integrations list without pagination and outgoing integration creation ([#16233](https://github.com/RocketChat/Rocket.Chat/pull/16233)) - Setup Wizard inputs and Admin Settings ([#16147](https://github.com/RocketChat/Rocket.Chat/pull/16147)) @@ -11681,16 +12890,13 @@ 🔍 Minor changes -- Release 2.4.2 ([#16274](https://github.com/RocketChat/Rocket.Chat/pull/16274) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Release 2.4.2 ([#16274](https://github.com/RocketChat/Rocket.Chat/pull/16274)) -### 👩‍💻👨‍💻 Contributors 😍 - -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@ggazzo](https://github.com/ggazzo) - [@sampaiodiego](https://github.com/sampaiodiego) - [@tassoevan](https://github.com/tassoevan) @@ -11752,61 +12958,61 @@ ### 🚀 Improvements -- Add deprecate warning in some unused publications ([#15935](https://github.com/RocketChat/Rocket.Chat/pull/15935) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add deprecate warning in some unused publications ([#15935](https://github.com/RocketChat/Rocket.Chat/pull/15935)) -- Livechat realtime dashboard ([#15792](https://github.com/RocketChat/Rocket.Chat/pull/15792) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Livechat realtime dashboard ([#15792](https://github.com/RocketChat/Rocket.Chat/pull/15792)) - Move 'Reply in Thread' button from menu to message actions ([#15685](https://github.com/RocketChat/Rocket.Chat/pull/15685) by [@antkaz](https://github.com/antkaz)) - Notify logged agents when their departments change ([#16033](https://github.com/RocketChat/Rocket.Chat/pull/16033)) -- Replace adminRooms publication by REST ([#15948](https://github.com/RocketChat/Rocket.Chat/pull/15948) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace adminRooms publication by REST ([#15948](https://github.com/RocketChat/Rocket.Chat/pull/15948)) -- Replace customSounds publication by REST ([#15907](https://github.com/RocketChat/Rocket.Chat/pull/15907) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace customSounds publication by REST ([#15907](https://github.com/RocketChat/Rocket.Chat/pull/15907)) -- Replace discussionsOfARoom publication by REST ([#15908](https://github.com/RocketChat/Rocket.Chat/pull/15908) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace discussionsOfARoom publication by REST ([#15908](https://github.com/RocketChat/Rocket.Chat/pull/15908)) -- Replace forgotten livechat:departmentAgents subscriptions ([#15970](https://github.com/RocketChat/Rocket.Chat/pull/15970) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace forgotten livechat:departmentAgents subscriptions ([#15970](https://github.com/RocketChat/Rocket.Chat/pull/15970)) -- Replace fullEmojiData publication by REST ([#15901](https://github.com/RocketChat/Rocket.Chat/pull/15901) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace fullEmojiData publication by REST ([#15901](https://github.com/RocketChat/Rocket.Chat/pull/15901)) -- Replace fullUserData publication by REST ([#15650](https://github.com/RocketChat/Rocket.Chat/pull/15650) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace fullUserData publication by REST ([#15650](https://github.com/RocketChat/Rocket.Chat/pull/15650)) -- Replace fullUserStatusData publication by REST ([#15942](https://github.com/RocketChat/Rocket.Chat/pull/15942) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace fullUserStatusData publication by REST ([#15942](https://github.com/RocketChat/Rocket.Chat/pull/15942)) -- Replace integrations and integrationHistory publications by REST ([#15885](https://github.com/RocketChat/Rocket.Chat/pull/15885) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace integrations and integrationHistory publications by REST ([#15885](https://github.com/RocketChat/Rocket.Chat/pull/15885)) -- Replace livechat:customFields to REST ([#15496](https://github.com/RocketChat/Rocket.Chat/pull/15496) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace livechat:customFields to REST ([#15496](https://github.com/RocketChat/Rocket.Chat/pull/15496)) -- Replace livechat:inquiry publication by REST and Streamer ([#15977](https://github.com/RocketChat/Rocket.Chat/pull/15977) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace livechat:inquiry publication by REST and Streamer ([#15977](https://github.com/RocketChat/Rocket.Chat/pull/15977)) -- Replace livechat:managers publication by REST ([#15944](https://github.com/RocketChat/Rocket.Chat/pull/15944) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace livechat:managers publication by REST ([#15944](https://github.com/RocketChat/Rocket.Chat/pull/15944)) -- Replace livechat:officeHour publication to REST ([#15503](https://github.com/RocketChat/Rocket.Chat/pull/15503) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace livechat:officeHour publication to REST ([#15503](https://github.com/RocketChat/Rocket.Chat/pull/15503)) -- Replace livechat:queue subscription ([#15612](https://github.com/RocketChat/Rocket.Chat/pull/15612) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace livechat:queue subscription ([#15612](https://github.com/RocketChat/Rocket.Chat/pull/15612)) -- Replace livechat:rooms publication by REST ([#15968](https://github.com/RocketChat/Rocket.Chat/pull/15968) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace livechat:rooms publication by REST ([#15968](https://github.com/RocketChat/Rocket.Chat/pull/15968)) -- Replace livechat:visitorHistory publication by REST ([#15943](https://github.com/RocketChat/Rocket.Chat/pull/15943) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace livechat:visitorHistory publication by REST ([#15943](https://github.com/RocketChat/Rocket.Chat/pull/15943)) -- Replace oauth publications by REST ([#15878](https://github.com/RocketChat/Rocket.Chat/pull/15878) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace oauth publications by REST ([#15878](https://github.com/RocketChat/Rocket.Chat/pull/15878)) -- Replace roles publication by REST ([#15910](https://github.com/RocketChat/Rocket.Chat/pull/15910) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace roles publication by REST ([#15910](https://github.com/RocketChat/Rocket.Chat/pull/15910)) -- Replace stdout publication by REST ([#16004](https://github.com/RocketChat/Rocket.Chat/pull/16004) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace stdout publication by REST ([#16004](https://github.com/RocketChat/Rocket.Chat/pull/16004)) -- Replace userAutocomplete publication by REST ([#15956](https://github.com/RocketChat/Rocket.Chat/pull/15956) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace userAutocomplete publication by REST ([#15956](https://github.com/RocketChat/Rocket.Chat/pull/15956)) -- Replace userData subscriptions by REST ([#15916](https://github.com/RocketChat/Rocket.Chat/pull/15916) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace userData subscriptions by REST ([#15916](https://github.com/RocketChat/Rocket.Chat/pull/15916)) -- Replace webdavAccounts publication by REST ([#15926](https://github.com/RocketChat/Rocket.Chat/pull/15926) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace webdavAccounts publication by REST ([#15926](https://github.com/RocketChat/Rocket.Chat/pull/15926)) -- Sorting on livechat analytics queries were wrong ([#16021](https://github.com/RocketChat/Rocket.Chat/pull/16021) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Sorting on livechat analytics queries were wrong ([#16021](https://github.com/RocketChat/Rocket.Chat/pull/16021)) - Update ui for Roles field ([#15888](https://github.com/RocketChat/Rocket.Chat/pull/15888) by [@antkaz](https://github.com/antkaz)) -- Validate user identity on send message process ([#15887](https://github.com/RocketChat/Rocket.Chat/pull/15887) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Validate user identity on send message process ([#15887](https://github.com/RocketChat/Rocket.Chat/pull/15887)) ### 🐛 Bug fixes @@ -11835,7 +13041,7 @@ - Error of bind environment on user data export ([#15985](https://github.com/RocketChat/Rocket.Chat/pull/15985)) -- Fix sort livechat rooms ([#16001](https://github.com/RocketChat/Rocket.Chat/pull/16001) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Fix sort livechat rooms ([#16001](https://github.com/RocketChat/Rocket.Chat/pull/16001)) - Guest's name field missing when forwarding livechat rooms ([#15991](https://github.com/RocketChat/Rocket.Chat/pull/15991)) @@ -11915,7 +13121,6 @@ ### 👩‍💻👨‍💻 Contributors 😍 -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@antkaz](https://github.com/antkaz) - [@ashwaniYDV](https://github.com/ashwaniYDV) - [@breaking-let](https://github.com/breaking-let) @@ -11930,6 +13135,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@MartinSchoeler](https://github.com/MartinSchoeler) - [@d-gubert](https://github.com/d-gubert) - [@gabriellsh](https://github.com/gabriellsh) @@ -12032,9 +13238,9 @@ - Allow Regexes on SAML user field mapping ([#15743](https://github.com/RocketChat/Rocket.Chat/pull/15743)) -- Livechat analytics ([#15230](https://github.com/RocketChat/Rocket.Chat/pull/15230) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Livechat analytics ([#15230](https://github.com/RocketChat/Rocket.Chat/pull/15230)) -- Livechat analytics functions ([#15666](https://github.com/RocketChat/Rocket.Chat/pull/15666) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Livechat analytics functions ([#15666](https://github.com/RocketChat/Rocket.Chat/pull/15666)) - Notify users when their email address change ([#15828](https://github.com/RocketChat/Rocket.Chat/pull/15828)) @@ -12051,7 +13257,7 @@ ### 🚀 Improvements -- Add more fields to iframe integration event `unread-changed-by-subscription` ([#15786](https://github.com/RocketChat/Rocket.Chat/pull/15786) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add more fields to iframe integration event `unread-changed-by-subscription` ([#15786](https://github.com/RocketChat/Rocket.Chat/pull/15786)) - Administration UI - React and Fuselage components ([#15452](https://github.com/RocketChat/Rocket.Chat/pull/15452)) @@ -12065,23 +13271,23 @@ - Make push notification batchsize and interval configurable ([#15804](https://github.com/RocketChat/Rocket.Chat/pull/15804) by [@Exordian](https://github.com/Exordian)) -- Remove "EmojiCustom" unused subscription ([#15658](https://github.com/RocketChat/Rocket.Chat/pull/15658) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove "EmojiCustom" unused subscription ([#15658](https://github.com/RocketChat/Rocket.Chat/pull/15658)) - remove computations inside messageAttachment ([#15716](https://github.com/RocketChat/Rocket.Chat/pull/15716)) -- Replace livechat:departmentAgents subscription to REST ([#15529](https://github.com/RocketChat/Rocket.Chat/pull/15529) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace livechat:departmentAgents subscription to REST ([#15529](https://github.com/RocketChat/Rocket.Chat/pull/15529)) -- Replace livechat:externalMessages publication by REST ([#15643](https://github.com/RocketChat/Rocket.Chat/pull/15643) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace livechat:externalMessages publication by REST ([#15643](https://github.com/RocketChat/Rocket.Chat/pull/15643)) -- Replace livechat:pagesvisited publication by REST ([#15629](https://github.com/RocketChat/Rocket.Chat/pull/15629) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace livechat:pagesvisited publication by REST ([#15629](https://github.com/RocketChat/Rocket.Chat/pull/15629)) -- Replace livechat:visitorInfo publication by REST ([#15639](https://github.com/RocketChat/Rocket.Chat/pull/15639) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace livechat:visitorInfo publication by REST ([#15639](https://github.com/RocketChat/Rocket.Chat/pull/15639)) -- Replace personalAccessTokens publication by REST ([#15644](https://github.com/RocketChat/Rocket.Chat/pull/15644) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace personalAccessTokens publication by REST ([#15644](https://github.com/RocketChat/Rocket.Chat/pull/15644)) -- Replace snippetedMessage publication by REST ([#15679](https://github.com/RocketChat/Rocket.Chat/pull/15679) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace snippetedMessage publication by REST ([#15679](https://github.com/RocketChat/Rocket.Chat/pull/15679)) -- Replace snipptedMessages publication by REST ([#15678](https://github.com/RocketChat/Rocket.Chat/pull/15678) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace snipptedMessages publication by REST ([#15678](https://github.com/RocketChat/Rocket.Chat/pull/15678)) - Unfollow own threads ([#15740](https://github.com/RocketChat/Rocket.Chat/pull/15740)) @@ -12120,17 +13326,17 @@ - Missing Privacy Policy Agree on register ([#15832](https://github.com/RocketChat/Rocket.Chat/pull/15832)) -- Not valid relative URLs on message attachments ([#15651](https://github.com/RocketChat/Rocket.Chat/pull/15651) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Not valid relative URLs on message attachments ([#15651](https://github.com/RocketChat/Rocket.Chat/pull/15651)) - Null value at Notifications Preferences tab ([#15638](https://github.com/RocketChat/Rocket.Chat/pull/15638)) - Pasting images on reply as thread ([#15811](https://github.com/RocketChat/Rocket.Chat/pull/15811)) -- Prevent agent last message undefined ([#15809](https://github.com/RocketChat/Rocket.Chat/pull/15809) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Prevent agent last message undefined ([#15809](https://github.com/RocketChat/Rocket.Chat/pull/15809)) - Push: fix notification priority for google (FCM) ([#15803](https://github.com/RocketChat/Rocket.Chat/pull/15803) by [@Exordian](https://github.com/Exordian)) -- REST endpoint `chat.syncMessages` returning an error with deleted messages ([#15824](https://github.com/RocketChat/Rocket.Chat/pull/15824) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- REST endpoint `chat.syncMessages` returning an error with deleted messages ([#15824](https://github.com/RocketChat/Rocket.Chat/pull/15824)) - Sending messages to livechat rooms without a subscription ([#15707](https://github.com/RocketChat/Rocket.Chat/pull/15707)) @@ -12146,7 +13352,7 @@ - [CHORE] Add lingohub to readme ([#15849](https://github.com/RocketChat/Rocket.Chat/pull/15849)) -- [REGRESSION] Add livechat room type to the room's file list ([#15795](https://github.com/RocketChat/Rocket.Chat/pull/15795) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- [REGRESSION] Add livechat room type to the room's file list ([#15795](https://github.com/RocketChat/Rocket.Chat/pull/15795)) - Fix Livechat duplicated templates error ([#15869](https://github.com/RocketChat/Rocket.Chat/pull/15869)) @@ -12183,7 +13389,6 @@ ### 👩‍💻👨‍💻 Contributors 😍 - [@Exordian](https://github.com/Exordian) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@mariaeduardacunha](https://github.com/mariaeduardacunha) - [@mpdbl](https://github.com/mpdbl) - [@nstseek](https://github.com/nstseek) @@ -12192,6 +13397,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@MartinSchoeler](https://github.com/MartinSchoeler) - [@d-gubert](https://github.com/d-gubert) - [@gabriellsh](https://github.com/gabriellsh) @@ -12238,11 +13444,11 @@ - Add new Livechat appearance setting to set the conversation finished message ([#15577](https://github.com/RocketChat/Rocket.Chat/pull/15577)) -- Add option to enable X-Frame-options header to avoid loading inside any Iframe ([#14698](https://github.com/RocketChat/Rocket.Chat/pull/14698) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add option to enable X-Frame-options header to avoid loading inside any Iframe ([#14698](https://github.com/RocketChat/Rocket.Chat/pull/14698)) -- Add users.requestDataDownload API endpoint ([#14428](https://github.com/RocketChat/Rocket.Chat/pull/14428) by [@Hudell](https://github.com/Hudell) & [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@ubarsaiyan](https://github.com/ubarsaiyan)) +- Add users.requestDataDownload API endpoint ([#14428](https://github.com/RocketChat/Rocket.Chat/pull/14428) by [@Hudell](https://github.com/Hudell) & [@ubarsaiyan](https://github.com/ubarsaiyan)) -- Added file type filter to RoomFiles ([#15289](https://github.com/RocketChat/Rocket.Chat/pull/15289) by [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@juanpetterson](https://github.com/juanpetterson)) +- Added file type filter to RoomFiles ([#15289](https://github.com/RocketChat/Rocket.Chat/pull/15289) by [@juanpetterson](https://github.com/juanpetterson)) - Assign new Livechat conversations to bot agents first ([#15317](https://github.com/RocketChat/Rocket.Chat/pull/15317)) @@ -12256,7 +13462,7 @@ - Remove all closed Livechat chats ([#13991](https://github.com/RocketChat/Rocket.Chat/pull/13991) by [@knrt10](https://github.com/knrt10)) -- Separate integration roles ([#13902](https://github.com/RocketChat/Rocket.Chat/pull/13902) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Separate integration roles ([#13902](https://github.com/RocketChat/Rocket.Chat/pull/13902)) - Thread support to apps slashcommands and slashcommand previews ([#15574](https://github.com/RocketChat/Rocket.Chat/pull/15574)) @@ -12273,25 +13479,25 @@ - Lazyload Katex Package ([#15398](https://github.com/RocketChat/Rocket.Chat/pull/15398)) -- Replace `livechat:departments` publication by REST Calls ([#15478](https://github.com/RocketChat/Rocket.Chat/pull/15478) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace `livechat:departments` publication by REST Calls ([#15478](https://github.com/RocketChat/Rocket.Chat/pull/15478)) -- Replace `livechat:triggers` publication by REST calls ([#15507](https://github.com/RocketChat/Rocket.Chat/pull/15507) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace `livechat:triggers` publication by REST calls ([#15507](https://github.com/RocketChat/Rocket.Chat/pull/15507)) -- Replace livechat:agents pub by REST calls ([#15490](https://github.com/RocketChat/Rocket.Chat/pull/15490) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace livechat:agents pub by REST calls ([#15490](https://github.com/RocketChat/Rocket.Chat/pull/15490)) -- Replace livechat:appearance pub to REST ([#15510](https://github.com/RocketChat/Rocket.Chat/pull/15510) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace livechat:appearance pub to REST ([#15510](https://github.com/RocketChat/Rocket.Chat/pull/15510)) -- Replace livechat:integration publication by REST ([#15607](https://github.com/RocketChat/Rocket.Chat/pull/15607) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace livechat:integration publication by REST ([#15607](https://github.com/RocketChat/Rocket.Chat/pull/15607)) -- Replace mentionedMessages publication to REST ([#15540](https://github.com/RocketChat/Rocket.Chat/pull/15540) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace mentionedMessages publication to REST ([#15540](https://github.com/RocketChat/Rocket.Chat/pull/15540)) -- Replace pinned messages subscription ([#15544](https://github.com/RocketChat/Rocket.Chat/pull/15544) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace pinned messages subscription ([#15544](https://github.com/RocketChat/Rocket.Chat/pull/15544)) -- Replace roomFilesWithSearchText subscription ([#15550](https://github.com/RocketChat/Rocket.Chat/pull/15550) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace roomFilesWithSearchText subscription ([#15550](https://github.com/RocketChat/Rocket.Chat/pull/15550)) -- Replace some livechat:rooms subscriptions ([#15532](https://github.com/RocketChat/Rocket.Chat/pull/15532) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace some livechat:rooms subscriptions ([#15532](https://github.com/RocketChat/Rocket.Chat/pull/15532)) -- Replace starred messages subscription ([#15548](https://github.com/RocketChat/Rocket.Chat/pull/15548) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Replace starred messages subscription ([#15548](https://github.com/RocketChat/Rocket.Chat/pull/15548)) - Secure cookies when using HTTPS connection ([#15500](https://github.com/RocketChat/Rocket.Chat/pull/15500)) @@ -12354,15 +13560,15 @@ - [CHORE] remove 'bulk-create-c' permission ([#15517](https://github.com/RocketChat/Rocket.Chat/pull/15517) by [@antkaz](https://github.com/antkaz)) -- [CHORE] Split logger classes to avoid cyclic dependencies ([#15559](https://github.com/RocketChat/Rocket.Chat/pull/15559) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- [CHORE] Split logger classes to avoid cyclic dependencies ([#15559](https://github.com/RocketChat/Rocket.Chat/pull/15559)) - [CHORE] Update latest Livechat widget version to 1.2.2 ([#15592](https://github.com/RocketChat/Rocket.Chat/pull/15592)) - [CHORE] Update latest Livechat widget version to 1.2.4 ([#15596](https://github.com/RocketChat/Rocket.Chat/pull/15596)) -- [FEATURE] Rest API upload file returns message object ([#13821](https://github.com/RocketChat/Rocket.Chat/pull/13821) by [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@knrt10](https://github.com/knrt10)) +- [FEATURE] Rest API upload file returns message object ([#13821](https://github.com/RocketChat/Rocket.Chat/pull/13821) by [@knrt10](https://github.com/knrt10)) -- [REGRESSION] Fix remove department from list ([#15591](https://github.com/RocketChat/Rocket.Chat/pull/15591) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- [REGRESSION] Fix remove department from list ([#15591](https://github.com/RocketChat/Rocket.Chat/pull/15591)) - Chore: Add Client Setup Information to Issue Template ([#15625](https://github.com/RocketChat/Rocket.Chat/pull/15625)) @@ -12378,7 +13584,7 @@ - Merge master into develop & Set version to 2.2.0-develop ([#15469](https://github.com/RocketChat/Rocket.Chat/pull/15469)) -- Move publication deprecation warnings ([#15676](https://github.com/RocketChat/Rocket.Chat/pull/15676) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Move publication deprecation warnings ([#15676](https://github.com/RocketChat/Rocket.Chat/pull/15676)) - New: Add dev dependency david badge to README ([#9058](https://github.com/RocketChat/Rocket.Chat/pull/9058) by [@robbyoconnor](https://github.com/robbyoconnor)) @@ -12394,7 +13600,7 @@ - Regression: hasPermission ignoring subscription roles ([#15652](https://github.com/RocketChat/Rocket.Chat/pull/15652)) -- Regression: Move import to avoid circular dependencies ([#15628](https://github.com/RocketChat/Rocket.Chat/pull/15628) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Regression: Move import to avoid circular dependencies ([#15628](https://github.com/RocketChat/Rocket.Chat/pull/15628)) - Regression: Remove reference to obsolete template helper ([#15675](https://github.com/RocketChat/Rocket.Chat/pull/15675)) @@ -12413,7 +13619,6 @@ ### 👩‍💻👨‍💻 Contributors 😍 - [@Hudell](https://github.com/Hudell) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@Montel](https://github.com/Montel) - [@RafaelGSS](https://github.com/RafaelGSS) - [@antkaz](https://github.com/antkaz) @@ -12432,6 +13637,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@MartinSchoeler](https://github.com/MartinSchoeler) - [@d-gubert](https://github.com/d-gubert) - [@geekgonecrazy](https://github.com/geekgonecrazy) @@ -12534,9 +13740,9 @@ ### 🎉 New features -- Add ability to disable email notifications globally ([#9667](https://github.com/RocketChat/Rocket.Chat/pull/9667) by [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@ferdifly](https://github.com/ferdifly)) +- Add ability to disable email notifications globally ([#9667](https://github.com/RocketChat/Rocket.Chat/pull/9667) by [@ferdifly](https://github.com/ferdifly)) -- Add JWT to uploaded files urls ([#15297](https://github.com/RocketChat/Rocket.Chat/pull/15297) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add JWT to uploaded files urls ([#15297](https://github.com/RocketChat/Rocket.Chat/pull/15297)) - Allow file sharing through Twilio(WhatsApp) integration ([#15415](https://github.com/RocketChat/Rocket.Chat/pull/15415)) @@ -12569,7 +13775,7 @@ - Add missing indices used by read receipts ([#15316](https://github.com/RocketChat/Rocket.Chat/pull/15316)) -- Add possibility of renaming a discussion ([#15122](https://github.com/RocketChat/Rocket.Chat/pull/15122) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add possibility of renaming a discussion ([#15122](https://github.com/RocketChat/Rocket.Chat/pull/15122)) - Administration UI ([#15401](https://github.com/RocketChat/Rocket.Chat/pull/15401)) @@ -12590,13 +13796,13 @@ ### 🐛 Bug fixes -- Add ENV VAR to enable users create token feature ([#15334](https://github.com/RocketChat/Rocket.Chat/pull/15334) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add ENV VAR to enable users create token feature ([#15334](https://github.com/RocketChat/Rocket.Chat/pull/15334)) - CAS users can take control of Rocket.Chat accounts ([#15346](https://github.com/RocketChat/Rocket.Chat/pull/15346)) -- Delivering real-time messages to users that left a room ([#15389](https://github.com/RocketChat/Rocket.Chat/pull/15389) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Delivering real-time messages to users that left a room ([#15389](https://github.com/RocketChat/Rocket.Chat/pull/15389)) -- Don't allow email violating whitelist addresses ([#15339](https://github.com/RocketChat/Rocket.Chat/pull/15339) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Don't allow email violating whitelist addresses ([#15339](https://github.com/RocketChat/Rocket.Chat/pull/15339)) - Double send bug on message box ([#15409](https://github.com/RocketChat/Rocket.Chat/pull/15409)) @@ -12606,13 +13812,13 @@ - Federation messages notifications ([#15418](https://github.com/RocketChat/Rocket.Chat/pull/15418)) -- Fix file uploads JWT ([#15412](https://github.com/RocketChat/Rocket.Chat/pull/15412) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Fix file uploads JWT ([#15412](https://github.com/RocketChat/Rocket.Chat/pull/15412)) - Grammatical error in Not Found page ([#15382](https://github.com/RocketChat/Rocket.Chat/pull/15382)) - LDAP usernames get additional '.' if they contain numbers ([#14644](https://github.com/RocketChat/Rocket.Chat/pull/14644) by [@Hudell](https://github.com/Hudell)) -- Limit exposed fields on some users. endpoints ([#15327](https://github.com/RocketChat/Rocket.Chat/pull/15327) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Limit exposed fields on some users. endpoints ([#15327](https://github.com/RocketChat/Rocket.Chat/pull/15327)) - Message box not centered ([#15367](https://github.com/RocketChat/Rocket.Chat/pull/15367)) @@ -12624,13 +13830,13 @@ - Reduce Message cache time to 500ms ([#15295](https://github.com/RocketChat/Rocket.Chat/pull/15295) by [@vickyokrm](https://github.com/vickyokrm)) -- REST API to return only public custom fields ([#15292](https://github.com/RocketChat/Rocket.Chat/pull/15292) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- REST API to return only public custom fields ([#15292](https://github.com/RocketChat/Rocket.Chat/pull/15292)) -- REST endpoint `users.setPreferences` to not override all user's preferences ([#15288](https://github.com/RocketChat/Rocket.Chat/pull/15288) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- REST endpoint `users.setPreferences` to not override all user's preferences ([#15288](https://github.com/RocketChat/Rocket.Chat/pull/15288)) - Set the DEFAULT_ECDH_CURVE to auto (#15245) ([#15365](https://github.com/RocketChat/Rocket.Chat/pull/15365) by [@dlundgren](https://github.com/dlundgren)) -- Subscription record not having the `ls` field ([#14544](https://github.com/RocketChat/Rocket.Chat/pull/14544) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Subscription record not having the `ls` field ([#14544](https://github.com/RocketChat/Rocket.Chat/pull/14544)) - User Profile Time Format ([#15385](https://github.com/RocketChat/Rocket.Chat/pull/15385)) @@ -12654,7 +13860,7 @@ - LingoHub based on develop ([#15377](https://github.com/RocketChat/Rocket.Chat/pull/15377)) -- Merge master into develop & Set version to 2.1.0-develop ([#15357](https://github.com/RocketChat/Rocket.Chat/pull/15357) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Merge master into develop & Set version to 2.1.0-develop ([#15357](https://github.com/RocketChat/Rocket.Chat/pull/15357)) - Regression: API CORS not working after Cordova being disabled by default ([#15443](https://github.com/RocketChat/Rocket.Chat/pull/15443)) @@ -12668,11 +13874,11 @@ - Regression: Messagebox height changing when typing ([#15380](https://github.com/RocketChat/Rocket.Chat/pull/15380)) -- Regression: Prevent parsing empty custom field setting ([#15413](https://github.com/RocketChat/Rocket.Chat/pull/15413) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Regression: Prevent parsing empty custom field setting ([#15413](https://github.com/RocketChat/Rocket.Chat/pull/15413)) - Regression: setup wizard dynamic import using relative url ([#15432](https://github.com/RocketChat/Rocket.Chat/pull/15432)) -- Remove GraphQL dependencies left ([#15356](https://github.com/RocketChat/Rocket.Chat/pull/15356) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove GraphQL dependencies left ([#15356](https://github.com/RocketChat/Rocket.Chat/pull/15356)) - Remove log ADMIN_PASS environment variable ([#15307](https://github.com/RocketChat/Rocket.Chat/pull/15307)) @@ -12687,7 +13893,6 @@ ### 👩‍💻👨‍💻 Contributors 😍 - [@Hudell](https://github.com/Hudell) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@dlundgren](https://github.com/dlundgren) - [@ferdifly](https://github.com/ferdifly) - [@ifantom](https://github.com/ifantom) @@ -12699,6 +13904,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@MartinSchoeler](https://github.com/MartinSchoeler) - [@alansikora](https://github.com/alansikora) - [@d-gubert](https://github.com/d-gubert) @@ -12758,7 +13964,7 @@ ### 🎉 New features -- Add autotranslate Rest endpoints ([#14885](https://github.com/RocketChat/Rocket.Chat/pull/14885) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add autotranslate Rest endpoints ([#14885](https://github.com/RocketChat/Rocket.Chat/pull/14885)) - Add Mobex to the list of SMS service providers ([#14655](https://github.com/RocketChat/Rocket.Chat/pull/14655) by [@zolbayars](https://github.com/zolbayars)) @@ -12766,7 +13972,7 @@ - Custom message popups ([#15117](https://github.com/RocketChat/Rocket.Chat/pull/15117) by [@Hudell](https://github.com/Hudell)) -- Endpoint to fetch livechat rooms with several filters ([#15155](https://github.com/RocketChat/Rocket.Chat/pull/15155) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Endpoint to fetch livechat rooms with several filters ([#15155](https://github.com/RocketChat/Rocket.Chat/pull/15155)) - Granular permissions for settings ([#8942](https://github.com/RocketChat/Rocket.Chat/pull/8942) by [@mrsimpson](https://github.com/mrsimpson)) @@ -12780,7 +13986,7 @@ - Options for SAML auth for individual organizations needs ([#14275](https://github.com/RocketChat/Rocket.Chat/pull/14275) by [@Deltachaos](https://github.com/Deltachaos) & [@Hudell](https://github.com/Hudell)) -- Rest API Endpoint to get pinned messages from a room ([#13864](https://github.com/RocketChat/Rocket.Chat/pull/13864) by [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@thayannevls](https://github.com/thayannevls)) +- Rest API Endpoint to get pinned messages from a room ([#13864](https://github.com/RocketChat/Rocket.Chat/pull/13864) by [@thayannevls](https://github.com/thayannevls)) - Setup Wizard and Page not found, using React components ([#15204](https://github.com/RocketChat/Rocket.Chat/pull/15204)) @@ -12789,11 +13995,11 @@ ### 🚀 Improvements -- Add asset extension validation ([#15088](https://github.com/RocketChat/Rocket.Chat/pull/15088) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add asset extension validation ([#15088](https://github.com/RocketChat/Rocket.Chat/pull/15088)) -- Add limit of 50 user's resume tokens ([#15102](https://github.com/RocketChat/Rocket.Chat/pull/15102) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add limit of 50 user's resume tokens ([#15102](https://github.com/RocketChat/Rocket.Chat/pull/15102)) -- Add possibility to use commands inside threads through Rest API ([#15167](https://github.com/RocketChat/Rocket.Chat/pull/15167) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add possibility to use commands inside threads through Rest API ([#15167](https://github.com/RocketChat/Rocket.Chat/pull/15167)) - Livechat User Management Improvements ([#14736](https://github.com/RocketChat/Rocket.Chat/pull/14736) by [@Hudell](https://github.com/Hudell)) @@ -12822,7 +14028,7 @@ - Messages search scroll ([#15175](https://github.com/RocketChat/Rocket.Chat/pull/15175)) -- Prevent to create discussion with empty name ([#14507](https://github.com/RocketChat/Rocket.Chat/pull/14507) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Prevent to create discussion with empty name ([#14507](https://github.com/RocketChat/Rocket.Chat/pull/14507)) - Rate limit incoming integrations (webhooks) ([#15038](https://github.com/RocketChat/Rocket.Chat/pull/15038) by [@mrsimpson](https://github.com/mrsimpson)) @@ -12840,7 +14046,7 @@ - User's auto complete showing everyone on the server ([#15212](https://github.com/RocketChat/Rocket.Chat/pull/15212)) -- Webdav crash ([#14918](https://github.com/RocketChat/Rocket.Chat/pull/14918) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Webdav crash ([#14918](https://github.com/RocketChat/Rocket.Chat/pull/14918))
🔍 Minor changes @@ -12852,7 +14058,7 @@ - Add wreiske to authorized users in catbot ([#15147](https://github.com/RocketChat/Rocket.Chat/pull/15147)) -- Allow file upload paths on attachments URLs ([#15121](https://github.com/RocketChat/Rocket.Chat/pull/15121) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Allow file upload paths on attachments URLs ([#15121](https://github.com/RocketChat/Rocket.Chat/pull/15121)) - Change notifications file imports to server ([#15184](https://github.com/RocketChat/Rocket.Chat/pull/15184)) @@ -12868,7 +14074,7 @@ - Fix v148 migration ([#15285](https://github.com/RocketChat/Rocket.Chat/pull/15285)) -- Improve url validation inside message object ([#15074](https://github.com/RocketChat/Rocket.Chat/pull/15074) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Improve url validation inside message object ([#15074](https://github.com/RocketChat/Rocket.Chat/pull/15074)) - LingoHub based on develop ([#15218](https://github.com/RocketChat/Rocket.Chat/pull/15218)) @@ -12930,7 +14136,6 @@ - [@Deltachaos](https://github.com/Deltachaos) - [@Hudell](https://github.com/Hudell) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@NatsumiKubo](https://github.com/NatsumiKubo) - [@cardoso](https://github.com/cardoso) - [@cesarmal](https://github.com/cesarmal) @@ -12947,6 +14152,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 - [@LuluGO](https://github.com/LuluGO) +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@MartinSchoeler](https://github.com/MartinSchoeler) - [@alansikora](https://github.com/alansikora) - [@d-gubert](https://github.com/d-gubert) @@ -13052,18 +14258,15 @@ 🔍 Minor changes -- Fix custom auth ([#15141](https://github.com/RocketChat/Rocket.Chat/pull/15141) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Fix custom auth ([#15141](https://github.com/RocketChat/Rocket.Chat/pull/15141)) -- Release 1.3.1 ([#15148](https://github.com/RocketChat/Rocket.Chat/pull/15148) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Release 1.3.1 ([#15148](https://github.com/RocketChat/Rocket.Chat/pull/15148))
-### 👩‍💻👨‍💻 Contributors 😍 - -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@ggazzo](https://github.com/ggazzo) - [@sampaiodiego](https://github.com/sampaiodiego) @@ -13099,7 +14302,7 @@ ### 🚀 Improvements -- Add descriptions on user data download buttons and popup info ([#14852](https://github.com/RocketChat/Rocket.Chat/pull/14852) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add descriptions on user data download buttons and popup info ([#14852](https://github.com/RocketChat/Rocket.Chat/pull/14852)) - Add flag to identify remote federation users ([#15004](https://github.com/RocketChat/Rocket.Chat/pull/15004)) @@ -13128,7 +14331,7 @@ - Edit message with arrow up key if not last message ([#15021](https://github.com/RocketChat/Rocket.Chat/pull/15021)) -- Edit permissions screen ([#14950](https://github.com/RocketChat/Rocket.Chat/pull/14950) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Edit permissions screen ([#14950](https://github.com/RocketChat/Rocket.Chat/pull/14950)) - eternal loading file list ([#14952](https://github.com/RocketChat/Rocket.Chat/pull/14952)) @@ -13144,9 +14347,9 @@ - Loading indicator positioning ([#14968](https://github.com/RocketChat/Rocket.Chat/pull/14968)) -- Message attachments not allowing float numbers ([#14412](https://github.com/RocketChat/Rocket.Chat/pull/14412) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Message attachments not allowing float numbers ([#14412](https://github.com/RocketChat/Rocket.Chat/pull/14412)) -- Method `getUsersOfRoom` not returning offline users if limit is not defined ([#14753](https://github.com/RocketChat/Rocket.Chat/pull/14753) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Method `getUsersOfRoom` not returning offline users if limit is not defined ([#14753](https://github.com/RocketChat/Rocket.Chat/pull/14753)) - Not being able to mention users with "all" and "here" usernames - do not allow users register that usernames ([#14468](https://github.com/RocketChat/Rocket.Chat/pull/14468) by [@hamidrezabstn](https://github.com/hamidrezabstn)) @@ -13156,7 +14359,7 @@ - OTR key icon missing on messages ([#14953](https://github.com/RocketChat/Rocket.Chat/pull/14953)) -- Prevent error on trying insert message with duplicated id ([#14945](https://github.com/RocketChat/Rocket.Chat/pull/14945) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Prevent error on trying insert message with duplicated id ([#14945](https://github.com/RocketChat/Rocket.Chat/pull/14945)) - Russian grammatical errors ([#14622](https://github.com/RocketChat/Rocket.Chat/pull/14622) by [@BehindLoader](https://github.com/BehindLoader)) @@ -13168,7 +14371,7 @@ - Typo in german translation ([#14833](https://github.com/RocketChat/Rocket.Chat/pull/14833) by [@Le-onardo](https://github.com/Le-onardo)) -- Users staying online after logout ([#14966](https://github.com/RocketChat/Rocket.Chat/pull/14966) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Users staying online after logout ([#14966](https://github.com/RocketChat/Rocket.Chat/pull/14966)) - users.setStatus REST endpoint not allowing reset status message ([#14916](https://github.com/RocketChat/Rocket.Chat/pull/14916) by [@cardoso](https://github.com/cardoso)) @@ -13186,7 +14389,7 @@ - Add missing French translation ([#15013](https://github.com/RocketChat/Rocket.Chat/pull/15013) by [@commiaI](https://github.com/commiaI)) -- Always convert the sha256 password to lowercase on checking ([#14941](https://github.com/RocketChat/Rocket.Chat/pull/14941) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Always convert the sha256 password to lowercase on checking ([#14941](https://github.com/RocketChat/Rocket.Chat/pull/14941)) - Bump jquery from 3.3.1 to 3.4.0 in /packages/rocketchat-livechat/.app ([#14922](https://github.com/RocketChat/Rocket.Chat/pull/14922) by [@dependabot[bot]](https://github.com/dependabot[bot])) @@ -13210,9 +14413,9 @@ - improve: relocate some of wizard info to register ([#14884](https://github.com/RocketChat/Rocket.Chat/pull/14884)) -- Merge master into develop & Set version to 1.3.0-develop ([#14889](https://github.com/RocketChat/Rocket.Chat/pull/14889) by [@Hudell](https://github.com/Hudell) & [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Merge master into develop & Set version to 1.3.0-develop ([#14889](https://github.com/RocketChat/Rocket.Chat/pull/14889) by [@Hudell](https://github.com/Hudell)) -- New: Apps and integrations statistics ([#14878](https://github.com/RocketChat/Rocket.Chat/pull/14878) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- New: Apps and integrations statistics ([#14878](https://github.com/RocketChat/Rocket.Chat/pull/14878)) - Regression: Apps and Marketplace UI issues ([#15045](https://github.com/RocketChat/Rocket.Chat/pull/15045)) @@ -13253,7 +14456,6 @@ - [@BehindLoader](https://github.com/BehindLoader) - [@Hudell](https://github.com/Hudell) - [@Le-onardo](https://github.com/Le-onardo) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@NateScarlet](https://github.com/NateScarlet) - [@anandpathak](https://github.com/anandpathak) - [@brakhane](https://github.com/brakhane) @@ -13272,6 +14474,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@alansikora](https://github.com/alansikora) - [@d-gubert](https://github.com/d-gubert) - [@engelgabriel](https://github.com/engelgabriel) @@ -13295,11 +14498,11 @@ 🔍 Minor changes -- Fix custom auth ([#15141](https://github.com/RocketChat/Rocket.Chat/pull/15141) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Fix custom auth ([#15141](https://github.com/RocketChat/Rocket.Chat/pull/15141)) -### 👩‍💻👨‍💻 Contributors 😍 +### 👩‍💻👨‍💻 Core Team 🤓 - [@MarcosSpessatto](https://github.com/MarcosSpessatto) @@ -13357,11 +14560,11 @@ ### 🎉 New features -- Add Livechat inquiries endpoints ([#14779](https://github.com/RocketChat/Rocket.Chat/pull/14779) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add Livechat inquiries endpoints ([#14779](https://github.com/RocketChat/Rocket.Chat/pull/14779)) - Add loading animation to webdav file picker ([#14759](https://github.com/RocketChat/Rocket.Chat/pull/14759) by [@ubarsaiyan](https://github.com/ubarsaiyan)) -- Add tmid property to outgoing integration ([#14699](https://github.com/RocketChat/Rocket.Chat/pull/14699) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add tmid property to outgoing integration ([#14699](https://github.com/RocketChat/Rocket.Chat/pull/14699)) - changed mongo version for snap from 3.2.7 to 3.4.20 ([#14838](https://github.com/RocketChat/Rocket.Chat/pull/14838)) @@ -13369,7 +14572,7 @@ - Custom User Status ([#13933](https://github.com/RocketChat/Rocket.Chat/pull/13933) by [@Hudell](https://github.com/Hudell) & [@wreiske](https://github.com/wreiske)) -- Endpoint to anonymously read channel's messages ([#14714](https://github.com/RocketChat/Rocket.Chat/pull/14714) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Endpoint to anonymously read channel's messages ([#14714](https://github.com/RocketChat/Rocket.Chat/pull/14714)) - Show App bundles and its apps ([#14886](https://github.com/RocketChat/Rocket.Chat/pull/14886)) @@ -13378,7 +14581,7 @@ - Add an optional rocketchat-protocol DNS entry for Federation ([#14589](https://github.com/RocketChat/Rocket.Chat/pull/14589)) -- Adds link to download generated user data file ([#14175](https://github.com/RocketChat/Rocket.Chat/pull/14175) by [@Hudell](https://github.com/Hudell) & [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Adds link to download generated user data file ([#14175](https://github.com/RocketChat/Rocket.Chat/pull/14175) by [@Hudell](https://github.com/Hudell)) - Layout of livechat manager pages to new style ([#13900](https://github.com/RocketChat/Rocket.Chat/pull/13900)) @@ -13393,19 +14596,19 @@ - Direct reply delete config and description ([#14493](https://github.com/RocketChat/Rocket.Chat/pull/14493) by [@ruKurz](https://github.com/ruKurz)) -- Error when using Download My Data or Export My Data ([#14645](https://github.com/RocketChat/Rocket.Chat/pull/14645) by [@Hudell](https://github.com/Hudell) & [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Error when using Download My Data or Export My Data ([#14645](https://github.com/RocketChat/Rocket.Chat/pull/14645) by [@Hudell](https://github.com/Hudell)) - Gap of messages when loading history when using threads ([#14837](https://github.com/RocketChat/Rocket.Chat/pull/14837)) - Import Chart.js error ([#14471](https://github.com/RocketChat/Rocket.Chat/pull/14471) by [@Hudell](https://github.com/Hudell) & [@sonbn0](https://github.com/sonbn0)) -- Increasing time to rate limit in shield.svg endpoint and add a setting to disable API rate limiter ([#14709](https://github.com/RocketChat/Rocket.Chat/pull/14709) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Increasing time to rate limit in shield.svg endpoint and add a setting to disable API rate limiter ([#14709](https://github.com/RocketChat/Rocket.Chat/pull/14709)) - LinkedIn OAuth login ([#14887](https://github.com/RocketChat/Rocket.Chat/pull/14887) by [@Hudell](https://github.com/Hudell)) -- Move the set Avatar call on user creation to make sure the user has username ([#14665](https://github.com/RocketChat/Rocket.Chat/pull/14665) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Move the set Avatar call on user creation to make sure the user has username ([#14665](https://github.com/RocketChat/Rocket.Chat/pull/14665)) -- Name is undefined in some emails ([#14533](https://github.com/RocketChat/Rocket.Chat/pull/14533) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Name is undefined in some emails ([#14533](https://github.com/RocketChat/Rocket.Chat/pull/14533)) - Removes E2E action button, icon and banner when E2E is disabled. ([#14810](https://github.com/RocketChat/Rocket.Chat/pull/14810)) @@ -13441,7 +14644,6 @@ - [@AnBo83](https://github.com/AnBo83) - [@Hudell](https://github.com/Hudell) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@knrt10](https://github.com/knrt10) - [@lolimay](https://github.com/lolimay) - [@mohamedar97](https://github.com/mohamedar97) @@ -13454,6 +14656,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 - [@LuluGO](https://github.com/LuluGO) +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@PrajvalRaval](https://github.com/PrajvalRaval) - [@alansikora](https://github.com/alansikora) - [@engelgabriel](https://github.com/engelgabriel) @@ -13476,11 +14679,11 @@ 🔍 Minor changes -- Fix custom auth ([#15141](https://github.com/RocketChat/Rocket.Chat/pull/15141) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Fix custom auth ([#15141](https://github.com/RocketChat/Rocket.Chat/pull/15141)) -### 👩‍💻👨‍💻 Contributors 😍 +### 👩‍💻👨‍💻 Core Team 🤓 - [@MarcosSpessatto](https://github.com/MarcosSpessatto) @@ -13540,27 +14743,27 @@ ### 🐛 Bug fixes -- Anonymous chat read ([#14717](https://github.com/RocketChat/Rocket.Chat/pull/14717) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Anonymous chat read ([#14717](https://github.com/RocketChat/Rocket.Chat/pull/14717)) - User Real Name being erased when not modified ([#14711](https://github.com/RocketChat/Rocket.Chat/pull/14711) by [@Hudell](https://github.com/Hudell)) -- User status information on User Info panel ([#14763](https://github.com/RocketChat/Rocket.Chat/pull/14763) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- User status information on User Info panel ([#14763](https://github.com/RocketChat/Rocket.Chat/pull/14763))
🔍 Minor changes -- Release 1.1.2 ([#14823](https://github.com/RocketChat/Rocket.Chat/pull/14823) by [@Hudell](https://github.com/Hudell) & [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Release 1.1.2 ([#14823](https://github.com/RocketChat/Rocket.Chat/pull/14823) by [@Hudell](https://github.com/Hudell))
### 👩‍💻👨‍💻 Contributors 😍 - [@Hudell](https://github.com/Hudell) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@ggazzo](https://github.com/ggazzo) - [@sampaiodiego](https://github.com/sampaiodiego) @@ -13615,7 +14818,7 @@ - Returns custom emojis through the Livechat REST API ([#14370](https://github.com/RocketChat/Rocket.Chat/pull/14370)) -- Setting option to mark as containing a secret/password ([#10273](https://github.com/RocketChat/Rocket.Chat/pull/10273) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Setting option to mark as containing a secret/password ([#10273](https://github.com/RocketChat/Rocket.Chat/pull/10273)) ### 🚀 Improvements @@ -13661,13 +14864,13 @@ - Channel settings form to textarea for Topic and Description ([#13328](https://github.com/RocketChat/Rocket.Chat/pull/13328) by [@supra08](https://github.com/supra08)) -- Custom scripts descriptions were not clear enough ([#14516](https://github.com/RocketChat/Rocket.Chat/pull/14516) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Custom scripts descriptions were not clear enough ([#14516](https://github.com/RocketChat/Rocket.Chat/pull/14516)) - Discussion name being invalid ([#14442](https://github.com/RocketChat/Rocket.Chat/pull/14442)) - Downloading files when running in sub directory ([#14485](https://github.com/RocketChat/Rocket.Chat/pull/14485) by [@miolane](https://github.com/miolane)) -- Duplicated link to jump to message ([#14505](https://github.com/RocketChat/Rocket.Chat/pull/14505) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Duplicated link to jump to message ([#14505](https://github.com/RocketChat/Rocket.Chat/pull/14505)) - E2E messages not decrypting in message threads ([#14580](https://github.com/RocketChat/Rocket.Chat/pull/14580)) @@ -13687,7 +14890,7 @@ - Fallback to mongo version that doesn't require clusterMonitor role ([#14403](https://github.com/RocketChat/Rocket.Chat/pull/14403)) -- Fix redirect to First channel after login ([#14434](https://github.com/RocketChat/Rocket.Chat/pull/14434) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Fix redirect to First channel after login ([#14434](https://github.com/RocketChat/Rocket.Chat/pull/14434)) - IE11 support ([#14422](https://github.com/RocketChat/Rocket.Chat/pull/14422)) @@ -13695,7 +14898,7 @@ - Inject code at the end of tag ([#14623](https://github.com/RocketChat/Rocket.Chat/pull/14623)) -- Mailer breaking if user doesn't have an email address ([#14614](https://github.com/RocketChat/Rocket.Chat/pull/14614) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Mailer breaking if user doesn't have an email address ([#14614](https://github.com/RocketChat/Rocket.Chat/pull/14614)) - Main thread title on replies ([#14372](https://github.com/RocketChat/Rocket.Chat/pull/14372)) @@ -13711,13 +14914,13 @@ - New day separator overlapping above system message ([#14362](https://github.com/RocketChat/Rocket.Chat/pull/14362)) -- No feedback when adding users that already exists in a room ([#14534](https://github.com/RocketChat/Rocket.Chat/pull/14534) by [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@gsunit](https://github.com/gsunit)) +- No feedback when adding users that already exists in a room ([#14534](https://github.com/RocketChat/Rocket.Chat/pull/14534) by [@gsunit](https://github.com/gsunit)) - Optional exit on Unhandled Promise Rejection ([#14291](https://github.com/RocketChat/Rocket.Chat/pull/14291)) - Popup cloud console in new window ([#14296](https://github.com/RocketChat/Rocket.Chat/pull/14296)) -- Pressing Enter in User Search field at channel causes reload ([#14388](https://github.com/RocketChat/Rocket.Chat/pull/14388) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Pressing Enter in User Search field at channel causes reload ([#14388](https://github.com/RocketChat/Rocket.Chat/pull/14388)) - preview pdf its not working ([#14419](https://github.com/RocketChat/Rocket.Chat/pull/14419)) @@ -13725,15 +14928,15 @@ - RocketChat client sending out video call requests unnecessarily ([#14496](https://github.com/RocketChat/Rocket.Chat/pull/14496)) -- Role `user` has being added after email verification even for non anonymous users ([#14263](https://github.com/RocketChat/Rocket.Chat/pull/14263) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Role `user` has being added after email verification even for non anonymous users ([#14263](https://github.com/RocketChat/Rocket.Chat/pull/14263)) - Role name spacing on Permissions page ([#14625](https://github.com/RocketChat/Rocket.Chat/pull/14625)) -- Room name was undefined in some info dialogs ([#14415](https://github.com/RocketChat/Rocket.Chat/pull/14415) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Room name was undefined in some info dialogs ([#14415](https://github.com/RocketChat/Rocket.Chat/pull/14415)) - SAML credentialToken removal was preventing mobile from being able to authenticate ([#14345](https://github.com/RocketChat/Rocket.Chat/pull/14345)) -- Save custom emoji with special characters causes some errors ([#14456](https://github.com/RocketChat/Rocket.Chat/pull/14456) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Save custom emoji with special characters causes some errors ([#14456](https://github.com/RocketChat/Rocket.Chat/pull/14456)) - Send replyTo for livechat offline messages ([#14568](https://github.com/RocketChat/Rocket.Chat/pull/14568)) @@ -13749,15 +14952,15 @@ - Unnecessary meteor.defer on openRoom ([#14396](https://github.com/RocketChat/Rocket.Chat/pull/14396)) -- Unread property of the room's lastMessage object was being wrong some times ([#13919](https://github.com/RocketChat/Rocket.Chat/pull/13919) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Unread property of the room's lastMessage object was being wrong some times ([#13919](https://github.com/RocketChat/Rocket.Chat/pull/13919)) - Users actions in administration were returning error ([#14400](https://github.com/RocketChat/Rocket.Chat/pull/14400)) -- Verify if the user is requesting your own information in users.info ([#14242](https://github.com/RocketChat/Rocket.Chat/pull/14242) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Verify if the user is requesting your own information in users.info ([#14242](https://github.com/RocketChat/Rocket.Chat/pull/14242)) - Wrong header at Apps admin section ([#14290](https://github.com/RocketChat/Rocket.Chat/pull/14290)) -- Wrong token name was generating error on Gitlab OAuth login ([#14379](https://github.com/RocketChat/Rocket.Chat/pull/14379) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Wrong token name was generating error on Gitlab OAuth login ([#14379](https://github.com/RocketChat/Rocket.Chat/pull/14379)) - You must join to view messages in this channel ([#14461](https://github.com/RocketChat/Rocket.Chat/pull/14461)) @@ -13771,13 +14974,13 @@ - [IMPROVEMENT] Don't group messages with different alias ([#14257](https://github.com/RocketChat/Rocket.Chat/pull/14257) by [@jungeonkim](https://github.com/jungeonkim)) -- [REGRESSION] Fix Slack bridge channel owner on channel creation ([#14565](https://github.com/RocketChat/Rocket.Chat/pull/14565) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- [REGRESSION] Fix Slack bridge channel owner on channel creation ([#14565](https://github.com/RocketChat/Rocket.Chat/pull/14565)) - Add digitalocean button to readme ([#14583](https://github.com/RocketChat/Rocket.Chat/pull/14583)) - Add missing german translations ([#14386](https://github.com/RocketChat/Rocket.Chat/pull/14386) by [@mrsimpson](https://github.com/mrsimpson)) -- Allow removing description, topic and annoucement of rooms(set as empty string) ([#13682](https://github.com/RocketChat/Rocket.Chat/pull/13682) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Allow removing description, topic and annoucement of rooms(set as empty string) ([#13682](https://github.com/RocketChat/Rocket.Chat/pull/13682)) - Ci improvements ([#14600](https://github.com/RocketChat/Rocket.Chat/pull/14600)) @@ -13842,7 +15045,6 @@ - [@AnBo83](https://github.com/AnBo83) - [@Hudell](https://github.com/Hudell) - [@Kailash0311](https://github.com/Kailash0311) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@arminfelder](https://github.com/arminfelder) - [@ashwaniYDV](https://github.com/ashwaniYDV) - [@bhardwajaditya](https://github.com/bhardwajaditya) @@ -13861,6 +15063,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@alansikora](https://github.com/alansikora) - [@d-gubert](https://github.com/d-gubert) - [@engelgabriel](https://github.com/engelgabriel) @@ -13883,11 +15086,11 @@ 🔍 Minor changes -- Fix custom auth ([#15141](https://github.com/RocketChat/Rocket.Chat/pull/15141) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Fix custom auth ([#15141](https://github.com/RocketChat/Rocket.Chat/pull/15141)) -### 👩‍💻👨‍💻 Contributors 😍 +### 👩‍💻👨‍💻 Core Team 🤓 - [@MarcosSpessatto](https://github.com/MarcosSpessatto) @@ -13920,17 +15123,17 @@ 🔍 Minor changes -- Release 1.0.3 ([#14446](https://github.com/RocketChat/Rocket.Chat/pull/14446) by [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@mrsimpson](https://github.com/mrsimpson)) +- Release 1.0.3 ([#14446](https://github.com/RocketChat/Rocket.Chat/pull/14446) by [@mrsimpson](https://github.com/mrsimpson)) ### 👩‍💻👨‍💻 Contributors 😍 -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@mrsimpson](https://github.com/mrsimpson) ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@engelgabriel](https://github.com/engelgabriel) - [@geekgonecrazy](https://github.com/geekgonecrazy) - [@ggazzo](https://github.com/ggazzo) @@ -14056,7 +15259,7 @@ - Remove deprecated file upload engine Slingshot ([#13724](https://github.com/RocketChat/Rocket.Chat/pull/13724)) -- Remove internal hubot package ([#13522](https://github.com/RocketChat/Rocket.Chat/pull/13522) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove internal hubot package ([#13522](https://github.com/RocketChat/Rocket.Chat/pull/13522)) - Require OPLOG/REPLICASET to run Rocket.Chat ([#14227](https://github.com/RocketChat/Rocket.Chat/pull/14227)) @@ -14073,13 +15276,13 @@ - Add message action to copy message to input as reply ([#12626](https://github.com/RocketChat/Rocket.Chat/pull/12626) by [@mrsimpson](https://github.com/mrsimpson)) -- Add missing remove add leader channel ([#13315](https://github.com/RocketChat/Rocket.Chat/pull/13315) by [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@Montel](https://github.com/Montel)) +- Add missing remove add leader channel ([#13315](https://github.com/RocketChat/Rocket.Chat/pull/13315) by [@Montel](https://github.com/Montel)) - Add offset parameter to channels.history, groups.history, dm.history ([#13310](https://github.com/RocketChat/Rocket.Chat/pull/13310) by [@xbolshe](https://github.com/xbolshe)) - Add parseUrls field to the apps message converter ([#13248](https://github.com/RocketChat/Rocket.Chat/pull/13248)) -- Add support to updatedSince parameter in emoji-custom.list and deprecated old endpoint ([#13510](https://github.com/RocketChat/Rocket.Chat/pull/13510) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add support to updatedSince parameter in emoji-custom.list and deprecated old endpoint ([#13510](https://github.com/RocketChat/Rocket.Chat/pull/13510)) - Add Voxtelesys to list of SMS providers ([#13697](https://github.com/RocketChat/Rocket.Chat/pull/13697) by [@jhnburke8](https://github.com/jhnburke8) & [@john08burke](https://github.com/john08burke)) @@ -14107,7 +15310,7 @@ - option to not use nrr (experimental) ([#14224](https://github.com/RocketChat/Rocket.Chat/pull/14224)) -- Permission to assign roles ([#13597](https://github.com/RocketChat/Rocket.Chat/pull/13597) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Permission to assign roles ([#13597](https://github.com/RocketChat/Rocket.Chat/pull/13597)) - Provide new Livechat client as community feature ([#13723](https://github.com/RocketChat/Rocket.Chat/pull/13723)) @@ -14115,9 +15318,9 @@ - REST endpoint to forward livechat rooms ([#13308](https://github.com/RocketChat/Rocket.Chat/pull/13308)) -- Rest endpoints of discussions ([#13987](https://github.com/RocketChat/Rocket.Chat/pull/13987) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Rest endpoints of discussions ([#13987](https://github.com/RocketChat/Rocket.Chat/pull/13987)) -- Rest threads ([#14045](https://github.com/RocketChat/Rocket.Chat/pull/14045) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Rest threads ([#14045](https://github.com/RocketChat/Rocket.Chat/pull/14045)) - Set up livechat connections created from new client ([#14236](https://github.com/RocketChat/Rocket.Chat/pull/14236)) @@ -14125,18 +15328,18 @@ - Threads V 1.0 ([#13996](https://github.com/RocketChat/Rocket.Chat/pull/13996)) -- Update message actions ([#14268](https://github.com/RocketChat/Rocket.Chat/pull/14268) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Update message actions ([#14268](https://github.com/RocketChat/Rocket.Chat/pull/14268)) - User avatars from external source ([#7929](https://github.com/RocketChat/Rocket.Chat/pull/7929) by [@mjovanovic0](https://github.com/mjovanovic0)) -- users.setActiveStatus endpoint in rest api ([#13443](https://github.com/RocketChat/Rocket.Chat/pull/13443) by [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@thayannevls](https://github.com/thayannevls)) +- users.setActiveStatus endpoint in rest api ([#13443](https://github.com/RocketChat/Rocket.Chat/pull/13443) by [@thayannevls](https://github.com/thayannevls)) ### 🚀 Improvements - Add decoding for commonName (cn) and displayName attributes for SAML ([#12347](https://github.com/RocketChat/Rocket.Chat/pull/12347) by [@pkolmann](https://github.com/pkolmann)) -- Add department field on find guest method ([#13491](https://github.com/RocketChat/Rocket.Chat/pull/13491) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add department field on find guest method ([#13491](https://github.com/RocketChat/Rocket.Chat/pull/13491)) - Add index for room's ts ([#13726](https://github.com/RocketChat/Rocket.Chat/pull/13726)) @@ -14205,7 +15408,7 @@ - .bin extension added to attached file names ([#13468](https://github.com/RocketChat/Rocket.Chat/pull/13468) by [@Hudell](https://github.com/Hudell)) -- Ability to activate an app installed by zip even offline ([#13563](https://github.com/RocketChat/Rocket.Chat/pull/13563) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Ability to activate an app installed by zip even offline ([#13563](https://github.com/RocketChat/Rocket.Chat/pull/13563)) - Add custom MIME types for *.ico extension ([#13969](https://github.com/RocketChat/Rocket.Chat/pull/13969)) @@ -14239,9 +15442,9 @@ - Bugfix markdown Marked link new tab ([#13245](https://github.com/RocketChat/Rocket.Chat/pull/13245) by [@DeviaVir](https://github.com/DeviaVir)) -- Change localStorage keys to work when server is running in a subdir ([#13968](https://github.com/RocketChat/Rocket.Chat/pull/13968) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Change localStorage keys to work when server is running in a subdir ([#13968](https://github.com/RocketChat/Rocket.Chat/pull/13968)) -- Change userId of rate limiter, change to logged user ([#13442](https://github.com/RocketChat/Rocket.Chat/pull/13442) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Change userId of rate limiter, change to logged user ([#13442](https://github.com/RocketChat/Rocket.Chat/pull/13442)) - Changing Room name updates the webhook ([#13672](https://github.com/RocketChat/Rocket.Chat/pull/13672) by [@knrt10](https://github.com/knrt10)) @@ -14259,15 +15462,15 @@ - Display first message when taking Livechat inquiry ([#13896](https://github.com/RocketChat/Rocket.Chat/pull/13896)) -- Do not allow change avatars of another users without permission ([#13629](https://github.com/RocketChat/Rocket.Chat/pull/13629) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Do not allow change avatars of another users without permission ([#13629](https://github.com/RocketChat/Rocket.Chat/pull/13629)) - Emoji detection at line breaks ([#13447](https://github.com/RocketChat/Rocket.Chat/pull/13447) by [@savish28](https://github.com/savish28)) - Empty result when getting badge count notification ([#14244](https://github.com/RocketChat/Rocket.Chat/pull/14244)) -- Error when recording data into the connection object ([#13553](https://github.com/RocketChat/Rocket.Chat/pull/13553) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Error when recording data into the connection object ([#13553](https://github.com/RocketChat/Rocket.Chat/pull/13553)) -- Fix bug when user try recreate channel or group with same name and remove room from cache when user leaves room ([#12341](https://github.com/RocketChat/Rocket.Chat/pull/12341) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Fix bug when user try recreate channel or group with same name and remove room from cache when user leaves room ([#12341](https://github.com/RocketChat/Rocket.Chat/pull/12341)) - Fix issue cannot filter channels by name ([#12952](https://github.com/RocketChat/Rocket.Chat/pull/12952) by [@huydang284](https://github.com/huydang284)) @@ -14275,7 +15478,7 @@ - Fix snap refresh hook ([#13702](https://github.com/RocketChat/Rocket.Chat/pull/13702)) -- Fix wrong this scope in Notifications ([#13515](https://github.com/RocketChat/Rocket.Chat/pull/13515) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Fix wrong this scope in Notifications ([#13515](https://github.com/RocketChat/Rocket.Chat/pull/13515)) - Fixed grammatical error. ([#13559](https://github.com/RocketChat/Rocket.Chat/pull/13559) by [@gsunit](https://github.com/gsunit)) @@ -14291,7 +15494,7 @@ - Get next Livechat agent endpoint ([#13485](https://github.com/RocketChat/Rocket.Chat/pull/13485)) -- Groups endpoints permission validations ([#13994](https://github.com/RocketChat/Rocket.Chat/pull/13994) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Groups endpoints permission validations ([#13994](https://github.com/RocketChat/Rocket.Chat/pull/13994)) - Handle showing/hiding input in messageBox ([#13564](https://github.com/RocketChat/Rocket.Chat/pull/13564)) @@ -14313,9 +15516,9 @@ - link of k8s deploy ([#13612](https://github.com/RocketChat/Rocket.Chat/pull/13612) by [@Mr-Linus](https://github.com/Mr-Linus)) -- Links and upload paths when running in a subdir ([#13982](https://github.com/RocketChat/Rocket.Chat/pull/13982) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Links and upload paths when running in a subdir ([#13982](https://github.com/RocketChat/Rocket.Chat/pull/13982)) -- Livechat office hours ([#14031](https://github.com/RocketChat/Rocket.Chat/pull/14031) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Livechat office hours ([#14031](https://github.com/RocketChat/Rocket.Chat/pull/14031)) - Livechat user registration in another department ([#10695](https://github.com/RocketChat/Rocket.Chat/pull/10695)) @@ -14357,19 +15560,19 @@ - Read Receipt for Livechat Messages fixed ([#13832](https://github.com/RocketChat/Rocket.Chat/pull/13832) by [@knrt10](https://github.com/knrt10)) -- Real names were not displayed in the reactions (API/UI) ([#13495](https://github.com/RocketChat/Rocket.Chat/pull/13495) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Real names were not displayed in the reactions (API/UI) ([#13495](https://github.com/RocketChat/Rocket.Chat/pull/13495)) - Receiving agent for new livechats from REST API ([#14103](https://github.com/RocketChat/Rocket.Chat/pull/14103)) - Remove Room info for Direct Messages (#9383) ([#12429](https://github.com/RocketChat/Rocket.Chat/pull/12429) by [@vinade](https://github.com/vinade)) -- Remove spaces in some i18n files ([#13801](https://github.com/RocketChat/Rocket.Chat/pull/13801) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove spaces in some i18n files ([#13801](https://github.com/RocketChat/Rocket.Chat/pull/13801)) - renderField template to correct short property usage ([#14148](https://github.com/RocketChat/Rocket.Chat/pull/14148)) - REST endpoint for creating custom emojis ([#13306](https://github.com/RocketChat/Rocket.Chat/pull/13306)) -- Restart required to apply changes in API Rate Limiter settings ([#13451](https://github.com/RocketChat/Rocket.Chat/pull/13451) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Restart required to apply changes in API Rate Limiter settings ([#13451](https://github.com/RocketChat/Rocket.Chat/pull/13451)) - Right arrows in default HTML content ([#13502](https://github.com/RocketChat/Rocket.Chat/pull/13502)) @@ -14377,11 +15580,11 @@ - Setup wizard calling 'saveSetting' for each field/setting ([#13349](https://github.com/RocketChat/Rocket.Chat/pull/13349)) -- Sidenav does not open on some admin pages ([#14010](https://github.com/RocketChat/Rocket.Chat/pull/14010) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Sidenav does not open on some admin pages ([#14010](https://github.com/RocketChat/Rocket.Chat/pull/14010)) - Sidenav mouse hover was slow ([#13482](https://github.com/RocketChat/Rocket.Chat/pull/13482)) -- Slackbridge private channels ([#14273](https://github.com/RocketChat/Rocket.Chat/pull/14273) by [@Hudell](https://github.com/Hudell) & [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@nylen](https://github.com/nylen)) +- Slackbridge private channels ([#14273](https://github.com/RocketChat/Rocket.Chat/pull/14273) by [@Hudell](https://github.com/Hudell) & [@nylen](https://github.com/nylen)) - Small improvements on message box ([#13444](https://github.com/RocketChat/Rocket.Chat/pull/13444)) @@ -14421,59 +15624,59 @@ 🔍 Minor changes -- Convert rocketchat-apps to main module structure ([#13409](https://github.com/RocketChat/Rocket.Chat/pull/13409) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-apps to main module structure ([#13409](https://github.com/RocketChat/Rocket.Chat/pull/13409)) -- Convert rocketchat-lib to main module structure ([#13415](https://github.com/RocketChat/Rocket.Chat/pull/13415) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-lib to main module structure ([#13415](https://github.com/RocketChat/Rocket.Chat/pull/13415)) -- Fix some imports from wrong packages, remove exports and files unused in rc-ui ([#13422](https://github.com/RocketChat/Rocket.Chat/pull/13422) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Fix some imports from wrong packages, remove exports and files unused in rc-ui ([#13422](https://github.com/RocketChat/Rocket.Chat/pull/13422)) -- Import missed functions to remove dependency of RC namespace ([#13414](https://github.com/RocketChat/Rocket.Chat/pull/13414) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Import missed functions to remove dependency of RC namespace ([#13414](https://github.com/RocketChat/Rocket.Chat/pull/13414)) -- Remove dependency of RC namespace in livechat/client ([#13370](https://github.com/RocketChat/Rocket.Chat/pull/13370) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RC namespace in livechat/client ([#13370](https://github.com/RocketChat/Rocket.Chat/pull/13370)) -- Remove dependency of RC namespace in rc-integrations and importer-hipchat-enterprise ([#13386](https://github.com/RocketChat/Rocket.Chat/pull/13386) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RC namespace in rc-integrations and importer-hipchat-enterprise ([#13386](https://github.com/RocketChat/Rocket.Chat/pull/13386)) -- Remove dependency of RC namespace in rc-livechat/server/publications ([#13383](https://github.com/RocketChat/Rocket.Chat/pull/13383) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RC namespace in rc-livechat/server/publications ([#13383](https://github.com/RocketChat/Rocket.Chat/pull/13383)) -- Remove dependency of RC namespace in rc-message-pin and message-snippet ([#13343](https://github.com/RocketChat/Rocket.Chat/pull/13343) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RC namespace in rc-message-pin and message-snippet ([#13343](https://github.com/RocketChat/Rocket.Chat/pull/13343)) -- Remove dependency of RC namespace in rc-oembed and rc-otr ([#13345](https://github.com/RocketChat/Rocket.Chat/pull/13345) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RC namespace in rc-oembed and rc-otr ([#13345](https://github.com/RocketChat/Rocket.Chat/pull/13345)) -- Remove dependency of RC namespace in rc-reactions, retention-policy and search ([#13347](https://github.com/RocketChat/Rocket.Chat/pull/13347) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RC namespace in rc-reactions, retention-policy and search ([#13347](https://github.com/RocketChat/Rocket.Chat/pull/13347)) -- Remove dependency of RC namespace in rc-slash-archiveroom, create, help, hide, invite, inviteall and join ([#13356](https://github.com/RocketChat/Rocket.Chat/pull/13356) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RC namespace in rc-slash-archiveroom, create, help, hide, invite, inviteall and join ([#13356](https://github.com/RocketChat/Rocket.Chat/pull/13356)) -- Remove dependency of RC namespace in rc-smarsh-connector, sms and spotify ([#13358](https://github.com/RocketChat/Rocket.Chat/pull/13358) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RC namespace in rc-smarsh-connector, sms and spotify ([#13358](https://github.com/RocketChat/Rocket.Chat/pull/13358)) -- Remove dependency of RC namespace in rc-statistics and tokenpass ([#13359](https://github.com/RocketChat/Rocket.Chat/pull/13359) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RC namespace in rc-statistics and tokenpass ([#13359](https://github.com/RocketChat/Rocket.Chat/pull/13359)) -- Remove dependency of RC namespace in rc-ui-master, ui-message- user-data-download and version-check ([#13365](https://github.com/RocketChat/Rocket.Chat/pull/13365) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RC namespace in rc-ui-master, ui-message- user-data-download and version-check ([#13365](https://github.com/RocketChat/Rocket.Chat/pull/13365)) -- Remove dependency of RC namespace in rc-ui, ui-account and ui-admin ([#13361](https://github.com/RocketChat/Rocket.Chat/pull/13361) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RC namespace in rc-ui, ui-account and ui-admin ([#13361](https://github.com/RocketChat/Rocket.Chat/pull/13361)) -- Remove dependency of RC namespace in rc-videobridge and webdav ([#13366](https://github.com/RocketChat/Rocket.Chat/pull/13366) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RC namespace in rc-videobridge and webdav ([#13366](https://github.com/RocketChat/Rocket.Chat/pull/13366)) -- Remove dependency of RC namespace in root client folder, imports/message-read-receipt and imports/personal-access-tokens ([#13389](https://github.com/RocketChat/Rocket.Chat/pull/13389) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RC namespace in root client folder, imports/message-read-receipt and imports/personal-access-tokens ([#13389](https://github.com/RocketChat/Rocket.Chat/pull/13389)) -- Remove dependency of RC namespace in root server folder - step 1 ([#13390](https://github.com/RocketChat/Rocket.Chat/pull/13390) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RC namespace in root server folder - step 1 ([#13390](https://github.com/RocketChat/Rocket.Chat/pull/13390)) -- Remove dependency of RC namespace in root server folder - step 4 ([#13400](https://github.com/RocketChat/Rocket.Chat/pull/13400) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RC namespace in root server folder - step 4 ([#13400](https://github.com/RocketChat/Rocket.Chat/pull/13400)) -- Remove functions from globals ([#13421](https://github.com/RocketChat/Rocket.Chat/pull/13421) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove functions from globals ([#13421](https://github.com/RocketChat/Rocket.Chat/pull/13421)) -- Remove LIvechat global variable from RC namespace ([#13378](https://github.com/RocketChat/Rocket.Chat/pull/13378) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove LIvechat global variable from RC namespace ([#13378](https://github.com/RocketChat/Rocket.Chat/pull/13378)) -- Remove unused files and code in rc-lib - step 1 ([#13416](https://github.com/RocketChat/Rocket.Chat/pull/13416) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove unused files and code in rc-lib - step 1 ([#13416](https://github.com/RocketChat/Rocket.Chat/pull/13416)) -- Remove unused files and code in rc-lib - step 3 ([#13420](https://github.com/RocketChat/Rocket.Chat/pull/13420) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove unused files and code in rc-lib - step 3 ([#13420](https://github.com/RocketChat/Rocket.Chat/pull/13420)) -- Remove unused files in rc-lib - step 2 ([#13419](https://github.com/RocketChat/Rocket.Chat/pull/13419) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove unused files in rc-lib - step 2 ([#13419](https://github.com/RocketChat/Rocket.Chat/pull/13419)) - [BUG] Icon Fixed for Knowledge base on Livechat ([#13806](https://github.com/RocketChat/Rocket.Chat/pull/13806) by [@knrt10](https://github.com/knrt10)) -- [New] Reply privately to group messages ([#14150](https://github.com/RocketChat/Rocket.Chat/pull/14150) by [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@bhardwajaditya](https://github.com/bhardwajaditya)) +- [New] Reply privately to group messages ([#14150](https://github.com/RocketChat/Rocket.Chat/pull/14150) by [@bhardwajaditya](https://github.com/bhardwajaditya)) -- [Regression] Fix integrations message example ([#14111](https://github.com/RocketChat/Rocket.Chat/pull/14111) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- [Regression] Fix integrations message example ([#14111](https://github.com/RocketChat/Rocket.Chat/pull/14111)) - [REGRESSION] Fix variable name references in message template ([#14184](https://github.com/RocketChat/Rocket.Chat/pull/14184)) @@ -14497,21 +15700,21 @@ - Broken styles in Administration's contextual bar ([#14222](https://github.com/RocketChat/Rocket.Chat/pull/14222)) -- Change dynamic dependency of FileUpload in Messages models ([#13776](https://github.com/RocketChat/Rocket.Chat/pull/13776) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Change dynamic dependency of FileUpload in Messages models ([#13776](https://github.com/RocketChat/Rocket.Chat/pull/13776)) - Change the way to resolve DNS for Federation ([#13695](https://github.com/RocketChat/Rocket.Chat/pull/13695)) - Convert imports to relative paths ([#13740](https://github.com/RocketChat/Rocket.Chat/pull/13740)) -- Convert rc-nrr and slashcommands open to main module structure ([#13520](https://github.com/RocketChat/Rocket.Chat/pull/13520) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rc-nrr and slashcommands open to main module structure ([#13520](https://github.com/RocketChat/Rocket.Chat/pull/13520)) - created function to allow change default values, fix loading search users ([#14177](https://github.com/RocketChat/Rocket.Chat/pull/14177)) - Depack: Use mainModule for root files ([#13508](https://github.com/RocketChat/Rocket.Chat/pull/13508)) -- Depackaging ([#13483](https://github.com/RocketChat/Rocket.Chat/pull/13483) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Depackaging ([#13483](https://github.com/RocketChat/Rocket.Chat/pull/13483)) -- Deprecate /api/v1/info in favor of /api/info ([#13798](https://github.com/RocketChat/Rocket.Chat/pull/13798) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Deprecate /api/v1/info in favor of /api/info ([#13798](https://github.com/RocketChat/Rocket.Chat/pull/13798)) - ESLint: Add more import rules ([#14226](https://github.com/RocketChat/Rocket.Chat/pull/14226)) @@ -14595,13 +15798,13 @@ - Lingohub sync and additional fixes ([#13825](https://github.com/RocketChat/Rocket.Chat/pull/13825)) -- Merge master into develop & Set version to 1.0.0-develop ([#13435](https://github.com/RocketChat/Rocket.Chat/pull/13435) by [@Hudell](https://github.com/Hudell) & [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@TkTech](https://github.com/TkTech) & [@theundefined](https://github.com/theundefined)) +- Merge master into develop & Set version to 1.0.0-develop ([#13435](https://github.com/RocketChat/Rocket.Chat/pull/13435) by [@Hudell](https://github.com/Hudell) & [@TkTech](https://github.com/TkTech) & [@theundefined](https://github.com/theundefined)) - Move LDAP Escape to login handler ([#14234](https://github.com/RocketChat/Rocket.Chat/pull/14234)) - Move mongo config away from cors package ([#13531](https://github.com/RocketChat/Rocket.Chat/pull/13531)) -- Move rc-livechat server models to rc-models ([#13384](https://github.com/RocketChat/Rocket.Chat/pull/13384) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Move rc-livechat server models to rc-models ([#13384](https://github.com/RocketChat/Rocket.Chat/pull/13384)) - New threads layout ([#14269](https://github.com/RocketChat/Rocket.Chat/pull/14269)) @@ -14619,7 +15822,7 @@ - Regression: Active room was not being marked ([#14276](https://github.com/RocketChat/Rocket.Chat/pull/14276)) -- Regression: Add debounce on admin users search to avoid blocking by DDP Rate Limiter ([#13529](https://github.com/RocketChat/Rocket.Chat/pull/13529) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Regression: Add debounce on admin users search to avoid blocking by DDP Rate Limiter ([#13529](https://github.com/RocketChat/Rocket.Chat/pull/13529)) - Regression: Add missing translations used in Apps pages ([#13674](https://github.com/RocketChat/Rocket.Chat/pull/13674)) @@ -14637,7 +15840,7 @@ - Regression: fix app pages styles ([#13567](https://github.com/RocketChat/Rocket.Chat/pull/13567)) -- Regression: Fix autolinker that was not parsing urls correctly ([#13497](https://github.com/RocketChat/Rocket.Chat/pull/13497) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Regression: Fix autolinker that was not parsing urls correctly ([#13497](https://github.com/RocketChat/Rocket.Chat/pull/13497)) - Regression: fix drop file ([#14225](https://github.com/RocketChat/Rocket.Chat/pull/14225)) @@ -14647,15 +15850,15 @@ - Regression: Fix icon for DMs ([#13679](https://github.com/RocketChat/Rocket.Chat/pull/13679)) -- Regression: Fix wrong imports in rc-models ([#13516](https://github.com/RocketChat/Rocket.Chat/pull/13516) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Regression: Fix wrong imports in rc-models ([#13516](https://github.com/RocketChat/Rocket.Chat/pull/13516)) - Regression: grouping messages on threads ([#14238](https://github.com/RocketChat/Rocket.Chat/pull/14238)) - Regression: Message box does not go back to initial state after sending a message ([#14161](https://github.com/RocketChat/Rocket.Chat/pull/14161)) -- Regression: Message box geolocation was throwing error ([#13496](https://github.com/RocketChat/Rocket.Chat/pull/13496) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Regression: Message box geolocation was throwing error ([#13496](https://github.com/RocketChat/Rocket.Chat/pull/13496)) -- Regression: Missing settings import at `packages/rocketchat-livechat/server/methods/saveAppearance.js` ([#13573](https://github.com/RocketChat/Rocket.Chat/pull/13573) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Regression: Missing settings import at `packages/rocketchat-livechat/server/methods/saveAppearance.js` ([#13573](https://github.com/RocketChat/Rocket.Chat/pull/13573)) - Regression: Not updating subscriptions and not showing desktop notifcations ([#13509](https://github.com/RocketChat/Rocket.Chat/pull/13509)) @@ -14687,33 +15890,33 @@ - Remove bitcoin link in Readme.md since the link is broken ([#13935](https://github.com/RocketChat/Rocket.Chat/pull/13935) by [@ashwaniYDV](https://github.com/ashwaniYDV)) -- Remove dependency of RC namespace in rc-livechat/imports, lib, server/api, server/hooks and server/lib ([#13379](https://github.com/RocketChat/Rocket.Chat/pull/13379) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RC namespace in rc-livechat/imports, lib, server/api, server/hooks and server/lib ([#13379](https://github.com/RocketChat/Rocket.Chat/pull/13379)) -- Remove dependency of RC namespace in rc-livechat/server/methods ([#13382](https://github.com/RocketChat/Rocket.Chat/pull/13382) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RC namespace in rc-livechat/server/methods ([#13382](https://github.com/RocketChat/Rocket.Chat/pull/13382)) -- Remove dependency of RC namespace in rc-livechat/server/models ([#13377](https://github.com/RocketChat/Rocket.Chat/pull/13377) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RC namespace in rc-livechat/server/models ([#13377](https://github.com/RocketChat/Rocket.Chat/pull/13377)) -- Remove dependency of RC namespace in rc-oauth2-server and message-star ([#13344](https://github.com/RocketChat/Rocket.Chat/pull/13344) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RC namespace in rc-oauth2-server and message-star ([#13344](https://github.com/RocketChat/Rocket.Chat/pull/13344)) -- Remove dependency of RC namespace in rc-setup-wizard, slackbridge and asciiarts ([#13348](https://github.com/RocketChat/Rocket.Chat/pull/13348) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RC namespace in rc-setup-wizard, slackbridge and asciiarts ([#13348](https://github.com/RocketChat/Rocket.Chat/pull/13348)) -- Remove dependency of RC namespace in rc-slash-kick, leave, me, msg, mute, open, topic and unarchiveroom ([#13357](https://github.com/RocketChat/Rocket.Chat/pull/13357) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RC namespace in rc-slash-kick, leave, me, msg, mute, open, topic and unarchiveroom ([#13357](https://github.com/RocketChat/Rocket.Chat/pull/13357)) -- Remove dependency of RC namespace in rc-ui-clean-history, ui-admin and ui-login ([#13362](https://github.com/RocketChat/Rocket.Chat/pull/13362) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RC namespace in rc-ui-clean-history, ui-admin and ui-login ([#13362](https://github.com/RocketChat/Rocket.Chat/pull/13362)) -- Remove dependency of RC namespace in rc-wordpress, chatpal-search and irc ([#13492](https://github.com/RocketChat/Rocket.Chat/pull/13492) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RC namespace in rc-wordpress, chatpal-search and irc ([#13492](https://github.com/RocketChat/Rocket.Chat/pull/13492)) -- Remove dependency of RC namespace in root server folder - step 2 ([#13397](https://github.com/RocketChat/Rocket.Chat/pull/13397) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RC namespace in root server folder - step 2 ([#13397](https://github.com/RocketChat/Rocket.Chat/pull/13397)) -- Remove dependency of RC namespace in root server folder - step 3 ([#13398](https://github.com/RocketChat/Rocket.Chat/pull/13398) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RC namespace in root server folder - step 3 ([#13398](https://github.com/RocketChat/Rocket.Chat/pull/13398)) -- Remove dependency of RC namespace in root server folder - step 5 ([#13402](https://github.com/RocketChat/Rocket.Chat/pull/13402) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RC namespace in root server folder - step 5 ([#13402](https://github.com/RocketChat/Rocket.Chat/pull/13402)) -- Remove dependency of RC namespace in root server folder - step 6 ([#13405](https://github.com/RocketChat/Rocket.Chat/pull/13405) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RC namespace in root server folder - step 6 ([#13405](https://github.com/RocketChat/Rocket.Chat/pull/13405)) -- Remove Npm.depends and Npm.require except those that are inside package.js ([#13518](https://github.com/RocketChat/Rocket.Chat/pull/13518) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove Npm.depends and Npm.require except those that are inside package.js ([#13518](https://github.com/RocketChat/Rocket.Chat/pull/13518)) -- Remove Package references ([#13523](https://github.com/RocketChat/Rocket.Chat/pull/13523) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove Package references ([#13523](https://github.com/RocketChat/Rocket.Chat/pull/13523)) - Remove Sandstorm support ([#13773](https://github.com/RocketChat/Rocket.Chat/pull/13773)) @@ -14729,7 +15932,7 @@ - Removed old templates ([#13406](https://github.com/RocketChat/Rocket.Chat/pull/13406)) -- Removing (almost) every dynamic imports ([#13767](https://github.com/RocketChat/Rocket.Chat/pull/13767) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Removing (almost) every dynamic imports ([#13767](https://github.com/RocketChat/Rocket.Chat/pull/13767)) - Rename Cloud to Connectivity Services & split Apps in Apps and Marketplace ([#14211](https://github.com/RocketChat/Rocket.Chat/pull/14211)) @@ -14763,7 +15966,7 @@ - Use main message as thread tab title ([#14213](https://github.com/RocketChat/Rocket.Chat/pull/14213)) -- Use own logic to get thread infos via REST ([#14210](https://github.com/RocketChat/Rocket.Chat/pull/14210) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Use own logic to get thread infos via REST ([#14210](https://github.com/RocketChat/Rocket.Chat/pull/14210)) - User remove role dialog fixed ([#13874](https://github.com/RocketChat/Rocket.Chat/pull/13874) by [@bhardwajaditya](https://github.com/bhardwajaditya)) @@ -14776,7 +15979,6 @@ - [@DeviaVir](https://github.com/DeviaVir) - [@Hudell](https://github.com/Hudell) - [@Kailash0311](https://github.com/Kailash0311) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@MohammedEssehemy](https://github.com/MohammedEssehemy) - [@Montel](https://github.com/Montel) - [@Mr-Linus](https://github.com/Mr-Linus) @@ -14825,6 +16027,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 - [@LuluGO](https://github.com/LuluGO) +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@alansikora](https://github.com/alansikora) - [@d-gubert](https://github.com/d-gubert) - [@engelgabriel](https://github.com/engelgabriel) @@ -14957,7 +16160,7 @@ ### 🐛 Bug fixes -- Fix bug when user try recreate channel or group with same name and remove room from cache when user leaves room ([#12341](https://github.com/RocketChat/Rocket.Chat/pull/12341) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Fix bug when user try recreate channel or group with same name and remove room from cache when user leaves room ([#12341](https://github.com/RocketChat/Rocket.Chat/pull/12341)) - HipChat Enterprise importer fails when importing a large amount of messages (millions) ([#13221](https://github.com/RocketChat/Rocket.Chat/pull/13221) by [@Hudell](https://github.com/Hudell)) @@ -14982,10 +16185,10 @@ ### 👩‍💻👨‍💻 Contributors 😍 - [@Hudell](https://github.com/Hudell) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@d-gubert](https://github.com/d-gubert) - [@geekgonecrazy](https://github.com/geekgonecrazy) - [@renatobecker](https://github.com/renatobecker) @@ -15004,19 +16207,19 @@ ### 🎉 New features -- Add Allow Methods directive to CORS ([#13073](https://github.com/RocketChat/Rocket.Chat/pull/13073) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add Allow Methods directive to CORS ([#13073](https://github.com/RocketChat/Rocket.Chat/pull/13073)) -- Add create, update and delete endpoint for custom emojis ([#13160](https://github.com/RocketChat/Rocket.Chat/pull/13160) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add create, update and delete endpoint for custom emojis ([#13160](https://github.com/RocketChat/Rocket.Chat/pull/13160)) - Add new Livechat REST endpoint to update the visitor's status ([#13108](https://github.com/RocketChat/Rocket.Chat/pull/13108)) -- Add rate limiter to REST endpoints ([#11251](https://github.com/RocketChat/Rocket.Chat/pull/11251) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add rate limiter to REST endpoints ([#11251](https://github.com/RocketChat/Rocket.Chat/pull/11251)) -- Added an option to disable email when activate and deactivate users ([#13183](https://github.com/RocketChat/Rocket.Chat/pull/13183) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Added an option to disable email when activate and deactivate users ([#13183](https://github.com/RocketChat/Rocket.Chat/pull/13183)) -- Added endpoint to update timeout of the jitsi video conference ([#13167](https://github.com/RocketChat/Rocket.Chat/pull/13167) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Added endpoint to update timeout of the jitsi video conference ([#13167](https://github.com/RocketChat/Rocket.Chat/pull/13167)) -- Added stream to notify when agent status change ([#13076](https://github.com/RocketChat/Rocket.Chat/pull/13076) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Added stream to notify when agent status change ([#13076](https://github.com/RocketChat/Rocket.Chat/pull/13076)) - Cloud Integration ([#13013](https://github.com/RocketChat/Rocket.Chat/pull/13013)) @@ -15049,7 +16252,7 @@ - Return room type field on Livechat findRoom method ([#13078](https://github.com/RocketChat/Rocket.Chat/pull/13078)) -- Return visitorEmails field on Livechat findGuest method ([#13097](https://github.com/RocketChat/Rocket.Chat/pull/13097) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Return visitorEmails field on Livechat findGuest method ([#13097](https://github.com/RocketChat/Rocket.Chat/pull/13097)) ### 🐛 Bug fixes @@ -15060,7 +16263,7 @@ - Change input type of e2e to password ([#13077](https://github.com/RocketChat/Rocket.Chat/pull/13077) by [@supra08](https://github.com/supra08)) -- Change webdav creation, due to changes in the npm lib after last update ([#13170](https://github.com/RocketChat/Rocket.Chat/pull/13170) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Change webdav creation, due to changes in the npm lib after last update ([#13170](https://github.com/RocketChat/Rocket.Chat/pull/13170)) - Emoticons not displayed in room topic ([#12858](https://github.com/RocketChat/Rocket.Chat/pull/12858) by [@alexbartsch](https://github.com/alexbartsch)) @@ -15076,7 +16279,7 @@ - REST api client base url on subdir ([#13180](https://github.com/RocketChat/Rocket.Chat/pull/13180)) -- REST API endpoint `users.getPersonalAccessTokens` error when user has no access tokens ([#13150](https://github.com/RocketChat/Rocket.Chat/pull/13150) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- REST API endpoint `users.getPersonalAccessTokens` error when user has no access tokens ([#13150](https://github.com/RocketChat/Rocket.Chat/pull/13150)) - Snap upgrade add post-refresh hook ([#13153](https://github.com/RocketChat/Rocket.Chat/pull/13153)) @@ -15088,75 +16291,75 @@ 🔍 Minor changes -- Remove dependency of RocketChat namespace and push-notifications ([#13137](https://github.com/RocketChat/Rocket.Chat/pull/13137) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RocketChat namespace and push-notifications ([#13137](https://github.com/RocketChat/Rocket.Chat/pull/13137)) - Change apps engine persistence bridge method to updateByAssociations ([#13239](https://github.com/RocketChat/Rocket.Chat/pull/13239)) -- Convert rocketchat-file-upload to main module structure ([#13094](https://github.com/RocketChat/Rocket.Chat/pull/13094) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-file-upload to main module structure ([#13094](https://github.com/RocketChat/Rocket.Chat/pull/13094)) -- Convert rocketchat-ui-master to main module structure ([#13107](https://github.com/RocketChat/Rocket.Chat/pull/13107) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-ui-master to main module structure ([#13107](https://github.com/RocketChat/Rocket.Chat/pull/13107)) -- Convert rocketchat-ui-sidenav to main module structure ([#13098](https://github.com/RocketChat/Rocket.Chat/pull/13098) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-ui-sidenav to main module structure ([#13098](https://github.com/RocketChat/Rocket.Chat/pull/13098)) -- Convert rocketchat-webrtc to main module structure ([#13117](https://github.com/RocketChat/Rocket.Chat/pull/13117) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-webrtc to main module structure ([#13117](https://github.com/RocketChat/Rocket.Chat/pull/13117)) -- Convert rocketchat:ui to main module structure ([#13132](https://github.com/RocketChat/Rocket.Chat/pull/13132) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat:ui to main module structure ([#13132](https://github.com/RocketChat/Rocket.Chat/pull/13132)) -- Globals/main module custom oauth ([#13037](https://github.com/RocketChat/Rocket.Chat/pull/13037) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Globals/main module custom oauth ([#13037](https://github.com/RocketChat/Rocket.Chat/pull/13037)) -- Globals/move rocketchat notifications ([#13035](https://github.com/RocketChat/Rocket.Chat/pull/13035) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Globals/move rocketchat notifications ([#13035](https://github.com/RocketChat/Rocket.Chat/pull/13035)) - Language: Edit typo "Обновлить" ([#13177](https://github.com/RocketChat/Rocket.Chat/pull/13177) by [@zpavlig](https://github.com/zpavlig)) - LingoHub based on develop ([#13201](https://github.com/RocketChat/Rocket.Chat/pull/13201)) -- Merge master into develop & Set version to 0.74.0-develop ([#13050](https://github.com/RocketChat/Rocket.Chat/pull/13050) by [@Hudell](https://github.com/Hudell) & [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@ohmonster](https://github.com/ohmonster) & [@piotrkochan](https://github.com/piotrkochan)) +- Merge master into develop & Set version to 0.74.0-develop ([#13050](https://github.com/RocketChat/Rocket.Chat/pull/13050) by [@Hudell](https://github.com/Hudell) & [@ohmonster](https://github.com/ohmonster) & [@piotrkochan](https://github.com/piotrkochan)) -- Move rocketchat models ([#13027](https://github.com/RocketChat/Rocket.Chat/pull/13027) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Move rocketchat models ([#13027](https://github.com/RocketChat/Rocket.Chat/pull/13027)) -- Move rocketchat promises ([#13039](https://github.com/RocketChat/Rocket.Chat/pull/13039) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Move rocketchat promises ([#13039](https://github.com/RocketChat/Rocket.Chat/pull/13039)) -- Move rocketchat settings to specific package ([#13026](https://github.com/RocketChat/Rocket.Chat/pull/13026) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Move rocketchat settings to specific package ([#13026](https://github.com/RocketChat/Rocket.Chat/pull/13026)) -- Move some function to utils ([#13122](https://github.com/RocketChat/Rocket.Chat/pull/13122) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Move some function to utils ([#13122](https://github.com/RocketChat/Rocket.Chat/pull/13122)) -- Move some ui function to ui-utils ([#13123](https://github.com/RocketChat/Rocket.Chat/pull/13123) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Move some ui function to ui-utils ([#13123](https://github.com/RocketChat/Rocket.Chat/pull/13123)) -- Move UI Collections to rocketchat:models ([#13064](https://github.com/RocketChat/Rocket.Chat/pull/13064) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Move UI Collections to rocketchat:models ([#13064](https://github.com/RocketChat/Rocket.Chat/pull/13064)) -- Move/create rocketchat callbacks ([#13034](https://github.com/RocketChat/Rocket.Chat/pull/13034) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Move/create rocketchat callbacks ([#13034](https://github.com/RocketChat/Rocket.Chat/pull/13034)) -- Move/create rocketchat metrics ([#13032](https://github.com/RocketChat/Rocket.Chat/pull/13032) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Move/create rocketchat metrics ([#13032](https://github.com/RocketChat/Rocket.Chat/pull/13032)) - Regression: Fix audio message upload ([#13224](https://github.com/RocketChat/Rocket.Chat/pull/13224)) - Regression: Fix emoji search ([#13207](https://github.com/RocketChat/Rocket.Chat/pull/13207)) -- Regression: Fix export AudioRecorder ([#13192](https://github.com/RocketChat/Rocket.Chat/pull/13192) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Regression: Fix export AudioRecorder ([#13192](https://github.com/RocketChat/Rocket.Chat/pull/13192)) - Regression: fix rooms model's collection name ([#13146](https://github.com/RocketChat/Rocket.Chat/pull/13146)) - Regression: fix upload permissions ([#13157](https://github.com/RocketChat/Rocket.Chat/pull/13157)) -- Release 0.74.0 ([#13270](https://github.com/RocketChat/Rocket.Chat/pull/13270) by [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@Xuhao](https://github.com/Xuhao) & [@supra08](https://github.com/supra08)) +- Release 0.74.0 ([#13270](https://github.com/RocketChat/Rocket.Chat/pull/13270) by [@Xuhao](https://github.com/Xuhao) & [@supra08](https://github.com/supra08)) -- Remove dependency between lib and authz ([#13066](https://github.com/RocketChat/Rocket.Chat/pull/13066) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency between lib and authz ([#13066](https://github.com/RocketChat/Rocket.Chat/pull/13066)) -- Remove dependency between RocketChat namespace and migrations ([#13133](https://github.com/RocketChat/Rocket.Chat/pull/13133) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency between RocketChat namespace and migrations ([#13133](https://github.com/RocketChat/Rocket.Chat/pull/13133)) -- Remove dependency of RocketChat namespace and custom-sounds ([#13136](https://github.com/RocketChat/Rocket.Chat/pull/13136) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RocketChat namespace and custom-sounds ([#13136](https://github.com/RocketChat/Rocket.Chat/pull/13136)) -- Remove dependency of RocketChat namespace and logger ([#13135](https://github.com/RocketChat/Rocket.Chat/pull/13135) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RocketChat namespace and logger ([#13135](https://github.com/RocketChat/Rocket.Chat/pull/13135)) -- Remove dependency of RocketChat namespace inside rocketchat:ui ([#13131](https://github.com/RocketChat/Rocket.Chat/pull/13131) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove dependency of RocketChat namespace inside rocketchat:ui ([#13131](https://github.com/RocketChat/Rocket.Chat/pull/13131)) -- Remove directly dependency between lib and e2e ([#13115](https://github.com/RocketChat/Rocket.Chat/pull/13115) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove directly dependency between lib and e2e ([#13115](https://github.com/RocketChat/Rocket.Chat/pull/13115)) -- Remove directly dependency between rocketchat:lib and emoji ([#13118](https://github.com/RocketChat/Rocket.Chat/pull/13118) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove directly dependency between rocketchat:lib and emoji ([#13118](https://github.com/RocketChat/Rocket.Chat/pull/13118)) - Remove incorrect pt-BR translation ([#13074](https://github.com/RocketChat/Rocket.Chat/pull/13074)) -- Rocketchat mailer ([#13036](https://github.com/RocketChat/Rocket.Chat/pull/13036) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Rocketchat mailer ([#13036](https://github.com/RocketChat/Rocket.Chat/pull/13036)) - Test only MongoDB with oplog versions 3.2 and 4.0 for PRs ([#13119](https://github.com/RocketChat/Rocket.Chat/pull/13119)) @@ -15167,7 +16370,6 @@ - [@Hudell](https://github.com/Hudell) - [@Jeroeny](https://github.com/Jeroeny) - [@Kailash0311](https://github.com/Kailash0311) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@Xuhao](https://github.com/Xuhao) - [@alexbartsch](https://github.com/alexbartsch) - [@behnejad](https://github.com/behnejad) @@ -15180,6 +16382,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 - [@LuluGO](https://github.com/LuluGO) +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@d-gubert](https://github.com/d-gubert) - [@geekgonecrazy](https://github.com/geekgonecrazy) - [@ggazzo](https://github.com/ggazzo) @@ -15264,17 +16467,17 @@ - /api/v1/spotlight: return joinCodeRequired field for rooms ([#12651](https://github.com/RocketChat/Rocket.Chat/pull/12651) by [@cardoso](https://github.com/cardoso)) -- Add permission to enable personal access token to specific roles ([#12309](https://github.com/RocketChat/Rocket.Chat/pull/12309) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add permission to enable personal access token to specific roles ([#12309](https://github.com/RocketChat/Rocket.Chat/pull/12309)) -- Add query parameter support to emoji-custom endpoint ([#12754](https://github.com/RocketChat/Rocket.Chat/pull/12754) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add query parameter support to emoji-custom endpoint ([#12754](https://github.com/RocketChat/Rocket.Chat/pull/12754)) - Added a link to contributing.md ([#12856](https://github.com/RocketChat/Rocket.Chat/pull/12856) by [@sanketsingh24](https://github.com/sanketsingh24)) -- Added chat.getDeletedMessages since specific date ([#13010](https://github.com/RocketChat/Rocket.Chat/pull/13010) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Added chat.getDeletedMessages since specific date ([#13010](https://github.com/RocketChat/Rocket.Chat/pull/13010)) - Config hooks for snap ([#12351](https://github.com/RocketChat/Rocket.Chat/pull/12351)) -- Create new permission.listAll endpoint to be able to use updatedSince parameter ([#12748](https://github.com/RocketChat/Rocket.Chat/pull/12748) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Create new permission.listAll endpoint to be able to use updatedSince parameter ([#12748](https://github.com/RocketChat/Rocket.Chat/pull/12748)) - Download button for each file in fileslist ([#12874](https://github.com/RocketChat/Rocket.Chat/pull/12874) by [@alexbartsch](https://github.com/alexbartsch)) @@ -15286,7 +16489,7 @@ - Mandatory 2fa for role ([#9748](https://github.com/RocketChat/Rocket.Chat/pull/9748) by [@Hudell](https://github.com/Hudell) & [@karlprieb](https://github.com/karlprieb)) -- New API Endpoints for the new version of JS SDK ([#12623](https://github.com/RocketChat/Rocket.Chat/pull/12623) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- New API Endpoints for the new version of JS SDK ([#12623](https://github.com/RocketChat/Rocket.Chat/pull/12623)) - Option to reset e2e key ([#12483](https://github.com/RocketChat/Rocket.Chat/pull/12483) by [@Hudell](https://github.com/Hudell)) @@ -15307,7 +16510,7 @@ - Add new acceptable header for Livechat REST requests ([#12561](https://github.com/RocketChat/Rocket.Chat/pull/12561)) -- Add rooms property in user object, if the user has the permission, with rooms roles ([#12105](https://github.com/RocketChat/Rocket.Chat/pull/12105) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add rooms property in user object, if the user has the permission, with rooms roles ([#12105](https://github.com/RocketChat/Rocket.Chat/pull/12105)) - Adding debugging instructions in README ([#12989](https://github.com/RocketChat/Rocket.Chat/pull/12989) by [@hypery2k](https://github.com/hypery2k)) @@ -15333,7 +16536,7 @@ - Ignore non-existent Livechat custom fields on Livechat API ([#12522](https://github.com/RocketChat/Rocket.Chat/pull/12522)) -- Improve unreads and unreadsFrom response, prevent it to be equal null ([#12563](https://github.com/RocketChat/Rocket.Chat/pull/12563) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Improve unreads and unreadsFrom response, prevent it to be equal null ([#12563](https://github.com/RocketChat/Rocket.Chat/pull/12563)) - Japanese translations ([#12382](https://github.com/RocketChat/Rocket.Chat/pull/12382) by [@ura14h](https://github.com/ura14h)) @@ -15368,7 +16571,7 @@ - Change field checks in RocketChat.saveStreamingOptions ([#12973](https://github.com/RocketChat/Rocket.Chat/pull/12973)) -- Change JSON to EJSON.parse query to support type Date ([#12706](https://github.com/RocketChat/Rocket.Chat/pull/12706) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Change JSON to EJSON.parse query to support type Date ([#12706](https://github.com/RocketChat/Rocket.Chat/pull/12706)) - Change registration message when user need to confirm email ([#9336](https://github.com/RocketChat/Rocket.Chat/pull/9336) by [@karlprieb](https://github.com/karlprieb)) @@ -15394,13 +16597,13 @@ - Exception in getSingleMessage ([#12970](https://github.com/RocketChat/Rocket.Chat/pull/12970) by [@tsukiRep](https://github.com/tsukiRep)) -- Fix favico error ([#12643](https://github.com/RocketChat/Rocket.Chat/pull/12643) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Fix favico error ([#12643](https://github.com/RocketChat/Rocket.Chat/pull/12643)) -- Fix set avatar http call, to avoid SSL errors ([#12790](https://github.com/RocketChat/Rocket.Chat/pull/12790) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Fix set avatar http call, to avoid SSL errors ([#12790](https://github.com/RocketChat/Rocket.Chat/pull/12790)) -- Fix users.setPreferences endpoint, set language correctly ([#12734](https://github.com/RocketChat/Rocket.Chat/pull/12734) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Fix users.setPreferences endpoint, set language correctly ([#12734](https://github.com/RocketChat/Rocket.Chat/pull/12734)) -- Fix wrong parameter in chat.delete endpoint and add some test cases ([#12408](https://github.com/RocketChat/Rocket.Chat/pull/12408) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Fix wrong parameter in chat.delete endpoint and add some test cases ([#12408](https://github.com/RocketChat/Rocket.Chat/pull/12408)) - Fixed Anonymous Registration ([#12633](https://github.com/RocketChat/Rocket.Chat/pull/12633) by [@wreiske](https://github.com/wreiske)) @@ -15412,7 +16615,7 @@ - high cpu usage ~ svg icon ([#12677](https://github.com/RocketChat/Rocket.Chat/pull/12677) by [@ph1p](https://github.com/ph1p)) -- Import missed file in rocketchat-authorization ([#12570](https://github.com/RocketChat/Rocket.Chat/pull/12570) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Import missed file in rocketchat-authorization ([#12570](https://github.com/RocketChat/Rocket.Chat/pull/12570)) - Incorrect parameter name in Livechat stream ([#12851](https://github.com/RocketChat/Rocket.Chat/pull/12851)) @@ -15430,9 +16633,9 @@ - PDF view loading indicator ([#12882](https://github.com/RocketChat/Rocket.Chat/pull/12882)) -- Pin and unpin message were not checking permissions ([#12739](https://github.com/RocketChat/Rocket.Chat/pull/12739) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Pin and unpin message were not checking permissions ([#12739](https://github.com/RocketChat/Rocket.Chat/pull/12739)) -- Prevent subscriptions and calls to rooms events that the user is not participating ([#12558](https://github.com/RocketChat/Rocket.Chat/pull/12558) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Prevent subscriptions and calls to rooms events that the user is not participating ([#12558](https://github.com/RocketChat/Rocket.Chat/pull/12558)) - Provide better Dutch translations 🇳🇱 ([#12792](https://github.com/RocketChat/Rocket.Chat/pull/12792) by [@mathysie](https://github.com/mathysie)) @@ -15468,27 +16671,27 @@ - Webdav integration account settings were being shown even when Webdav was disabled ([#12569](https://github.com/RocketChat/Rocket.Chat/pull/12569) by [@karakayasemi](https://github.com/karakayasemi)) -- Wrong test case for `users.setAvatar` endpoint ([#12539](https://github.com/RocketChat/Rocket.Chat/pull/12539) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Wrong test case for `users.setAvatar` endpoint ([#12539](https://github.com/RocketChat/Rocket.Chat/pull/12539))
🔍 Minor changes -- Convert rocketchat-channel-settings to main module structure ([#12594](https://github.com/RocketChat/Rocket.Chat/pull/12594) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-channel-settings to main module structure ([#12594](https://github.com/RocketChat/Rocket.Chat/pull/12594)) -- Convert rocketchat-emoji-custom to main module structure ([#12604](https://github.com/RocketChat/Rocket.Chat/pull/12604) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-emoji-custom to main module structure ([#12604](https://github.com/RocketChat/Rocket.Chat/pull/12604)) -- Convert rocketchat-importer-slack to main module structure ([#12666](https://github.com/RocketChat/Rocket.Chat/pull/12666) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-importer-slack to main module structure ([#12666](https://github.com/RocketChat/Rocket.Chat/pull/12666)) -- Convert rocketchat-livestream to main module structure ([#12679](https://github.com/RocketChat/Rocket.Chat/pull/12679) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-livestream to main module structure ([#12679](https://github.com/RocketChat/Rocket.Chat/pull/12679)) -- Convert rocketchat-mentions-flextab to main module structure ([#12757](https://github.com/RocketChat/Rocket.Chat/pull/12757) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-mentions-flextab to main module structure ([#12757](https://github.com/RocketChat/Rocket.Chat/pull/12757)) -- Convert rocketchat-reactions to main module structure ([#12888](https://github.com/RocketChat/Rocket.Chat/pull/12888) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-reactions to main module structure ([#12888](https://github.com/RocketChat/Rocket.Chat/pull/12888)) -- Convert rocketchat-ui-account to main module structure ([#12842](https://github.com/RocketChat/Rocket.Chat/pull/12842) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-ui-account to main module structure ([#12842](https://github.com/RocketChat/Rocket.Chat/pull/12842)) -- Convert rocketchat-ui-flextab to main module structure ([#12859](https://github.com/RocketChat/Rocket.Chat/pull/12859) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-ui-flextab to main module structure ([#12859](https://github.com/RocketChat/Rocket.Chat/pull/12859)) - [DOCS] Remove Cordova links, include F-Droid download button and few other adjustments ([#12583](https://github.com/RocketChat/Rocket.Chat/pull/12583) by [@rafaelks](https://github.com/rafaelks)) @@ -15496,215 +16699,215 @@ - Added "npm install" to quick start for developers ([#12374](https://github.com/RocketChat/Rocket.Chat/pull/12374) by [@wreiske](https://github.com/wreiske)) -- Added imports for global variables in rocketchat-google-natural-language package ([#12647](https://github.com/RocketChat/Rocket.Chat/pull/12647) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Added imports for global variables in rocketchat-google-natural-language package ([#12647](https://github.com/RocketChat/Rocket.Chat/pull/12647)) - Bump Apps Engine to 1.3.0 ([#12705](https://github.com/RocketChat/Rocket.Chat/pull/12705)) -- Change `chat.getDeletedMessages` to get messages after informed date and return only message's _id ([#13021](https://github.com/RocketChat/Rocket.Chat/pull/13021) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Change `chat.getDeletedMessages` to get messages after informed date and return only message's _id ([#13021](https://github.com/RocketChat/Rocket.Chat/pull/13021)) - changed maxRoomsOpen ([#12949](https://github.com/RocketChat/Rocket.Chat/pull/12949)) -- Convert chatpal search package to modular structure ([#12485](https://github.com/RocketChat/Rocket.Chat/pull/12485) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert chatpal search package to modular structure ([#12485](https://github.com/RocketChat/Rocket.Chat/pull/12485)) -- Convert emoji-emojione to main module structure ([#12605](https://github.com/RocketChat/Rocket.Chat/pull/12605) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert emoji-emojione to main module structure ([#12605](https://github.com/RocketChat/Rocket.Chat/pull/12605)) -- Convert meteor-accounts-saml to main module structure ([#12486](https://github.com/RocketChat/Rocket.Chat/pull/12486) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert meteor-accounts-saml to main module structure ([#12486](https://github.com/RocketChat/Rocket.Chat/pull/12486)) -- Convert meteor-autocomplete package to main module structure ([#12491](https://github.com/RocketChat/Rocket.Chat/pull/12491) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert meteor-autocomplete package to main module structure ([#12491](https://github.com/RocketChat/Rocket.Chat/pull/12491)) -- Convert meteor-timesync to main module structure ([#12495](https://github.com/RocketChat/Rocket.Chat/pull/12495) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert meteor-timesync to main module structure ([#12495](https://github.com/RocketChat/Rocket.Chat/pull/12495)) -- Convert rocketchat-2fa to main module structure ([#12501](https://github.com/RocketChat/Rocket.Chat/pull/12501) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-2fa to main module structure ([#12501](https://github.com/RocketChat/Rocket.Chat/pull/12501)) -- Convert rocketchat-action-links to main module structure ([#12503](https://github.com/RocketChat/Rocket.Chat/pull/12503) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-action-links to main module structure ([#12503](https://github.com/RocketChat/Rocket.Chat/pull/12503)) -- Convert rocketchat-analytics to main module structure ([#12506](https://github.com/RocketChat/Rocket.Chat/pull/12506) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-analytics to main module structure ([#12506](https://github.com/RocketChat/Rocket.Chat/pull/12506)) -- Convert rocketchat-api to main module structure ([#12510](https://github.com/RocketChat/Rocket.Chat/pull/12510) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-api to main module structure ([#12510](https://github.com/RocketChat/Rocket.Chat/pull/12510)) -- Convert rocketchat-assets to main module structure ([#12521](https://github.com/RocketChat/Rocket.Chat/pull/12521) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-assets to main module structure ([#12521](https://github.com/RocketChat/Rocket.Chat/pull/12521)) -- Convert rocketchat-authorization to main module structure ([#12523](https://github.com/RocketChat/Rocket.Chat/pull/12523) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-authorization to main module structure ([#12523](https://github.com/RocketChat/Rocket.Chat/pull/12523)) -- Convert rocketchat-autolinker to main module structure ([#12529](https://github.com/RocketChat/Rocket.Chat/pull/12529) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-autolinker to main module structure ([#12529](https://github.com/RocketChat/Rocket.Chat/pull/12529)) -- Convert rocketchat-autotranslate to main module structure ([#12530](https://github.com/RocketChat/Rocket.Chat/pull/12530) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-autotranslate to main module structure ([#12530](https://github.com/RocketChat/Rocket.Chat/pull/12530)) -- Convert rocketchat-bot-helpers to main module structure ([#12531](https://github.com/RocketChat/Rocket.Chat/pull/12531) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-bot-helpers to main module structure ([#12531](https://github.com/RocketChat/Rocket.Chat/pull/12531)) -- Convert rocketchat-cas to main module structure ([#12532](https://github.com/RocketChat/Rocket.Chat/pull/12532) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-cas to main module structure ([#12532](https://github.com/RocketChat/Rocket.Chat/pull/12532)) -- Convert rocketchat-channel-settings-mail-messages to main module structure ([#12537](https://github.com/RocketChat/Rocket.Chat/pull/12537) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-channel-settings-mail-messages to main module structure ([#12537](https://github.com/RocketChat/Rocket.Chat/pull/12537)) -- Convert rocketchat-colors to main module structure ([#12538](https://github.com/RocketChat/Rocket.Chat/pull/12538) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-colors to main module structure ([#12538](https://github.com/RocketChat/Rocket.Chat/pull/12538)) -- Convert rocketchat-cors to main module structure ([#12595](https://github.com/RocketChat/Rocket.Chat/pull/12595) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-cors to main module structure ([#12595](https://github.com/RocketChat/Rocket.Chat/pull/12595)) -- Convert rocketchat-crowd to main module structure ([#12596](https://github.com/RocketChat/Rocket.Chat/pull/12596) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-crowd to main module structure ([#12596](https://github.com/RocketChat/Rocket.Chat/pull/12596)) -- Convert rocketchat-custom-sounds to main module structure ([#12599](https://github.com/RocketChat/Rocket.Chat/pull/12599) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-custom-sounds to main module structure ([#12599](https://github.com/RocketChat/Rocket.Chat/pull/12599)) -- Convert rocketchat-dolphin to main module structure ([#12600](https://github.com/RocketChat/Rocket.Chat/pull/12600) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-dolphin to main module structure ([#12600](https://github.com/RocketChat/Rocket.Chat/pull/12600)) -- Convert rocketchat-drupal to main module structure ([#12601](https://github.com/RocketChat/Rocket.Chat/pull/12601) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-drupal to main module structure ([#12601](https://github.com/RocketChat/Rocket.Chat/pull/12601)) -- Convert rocketchat-emoji to main module structure ([#12603](https://github.com/RocketChat/Rocket.Chat/pull/12603) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-emoji to main module structure ([#12603](https://github.com/RocketChat/Rocket.Chat/pull/12603)) -- Convert rocketchat-error-handler to main module structure ([#12606](https://github.com/RocketChat/Rocket.Chat/pull/12606) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-error-handler to main module structure ([#12606](https://github.com/RocketChat/Rocket.Chat/pull/12606)) -- Convert rocketchat-favico to main module structure ([#12607](https://github.com/RocketChat/Rocket.Chat/pull/12607) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-favico to main module structure ([#12607](https://github.com/RocketChat/Rocket.Chat/pull/12607)) -- Convert rocketchat-file to main module structure ([#12644](https://github.com/RocketChat/Rocket.Chat/pull/12644) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-file to main module structure ([#12644](https://github.com/RocketChat/Rocket.Chat/pull/12644)) -- Convert rocketchat-github-enterprise to main module structure ([#12642](https://github.com/RocketChat/Rocket.Chat/pull/12642) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-github-enterprise to main module structure ([#12642](https://github.com/RocketChat/Rocket.Chat/pull/12642)) -- Convert rocketchat-gitlab to main module structure ([#12646](https://github.com/RocketChat/Rocket.Chat/pull/12646) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-gitlab to main module structure ([#12646](https://github.com/RocketChat/Rocket.Chat/pull/12646)) -- Convert rocketchat-google-vision to main module structure ([#12649](https://github.com/RocketChat/Rocket.Chat/pull/12649) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-google-vision to main module structure ([#12649](https://github.com/RocketChat/Rocket.Chat/pull/12649)) -- Convert rocketchat-grant to main module structure ([#12657](https://github.com/RocketChat/Rocket.Chat/pull/12657) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-grant to main module structure ([#12657](https://github.com/RocketChat/Rocket.Chat/pull/12657)) -- Convert rocketchat-graphql to main module structure ([#12658](https://github.com/RocketChat/Rocket.Chat/pull/12658) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-graphql to main module structure ([#12658](https://github.com/RocketChat/Rocket.Chat/pull/12658)) -- Convert rocketchat-highlight-words to main module structure ([#12659](https://github.com/RocketChat/Rocket.Chat/pull/12659) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-highlight-words to main module structure ([#12659](https://github.com/RocketChat/Rocket.Chat/pull/12659)) -- Convert rocketchat-iframe-login to main module structure ([#12661](https://github.com/RocketChat/Rocket.Chat/pull/12661) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-iframe-login to main module structure ([#12661](https://github.com/RocketChat/Rocket.Chat/pull/12661)) -- Convert rocketchat-importer to main module structure ([#12662](https://github.com/RocketChat/Rocket.Chat/pull/12662) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-importer to main module structure ([#12662](https://github.com/RocketChat/Rocket.Chat/pull/12662)) -- Convert rocketchat-importer-csv to main module structure ([#12663](https://github.com/RocketChat/Rocket.Chat/pull/12663) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-importer-csv to main module structure ([#12663](https://github.com/RocketChat/Rocket.Chat/pull/12663)) -- Convert rocketchat-importer-hipchat to main module structure ([#12664](https://github.com/RocketChat/Rocket.Chat/pull/12664) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-importer-hipchat to main module structure ([#12664](https://github.com/RocketChat/Rocket.Chat/pull/12664)) -- Convert rocketchat-importer-hipchat-enterprise to main module structure ([#12665](https://github.com/RocketChat/Rocket.Chat/pull/12665) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-importer-hipchat-enterprise to main module structure ([#12665](https://github.com/RocketChat/Rocket.Chat/pull/12665)) -- Convert rocketchat-importer-slack-users to main module structure ([#12669](https://github.com/RocketChat/Rocket.Chat/pull/12669) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-importer-slack-users to main module structure ([#12669](https://github.com/RocketChat/Rocket.Chat/pull/12669)) -- Convert rocketchat-integrations to main module structure ([#12670](https://github.com/RocketChat/Rocket.Chat/pull/12670) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-integrations to main module structure ([#12670](https://github.com/RocketChat/Rocket.Chat/pull/12670)) -- Convert rocketchat-internal-hubot to main module structure ([#12671](https://github.com/RocketChat/Rocket.Chat/pull/12671) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-internal-hubot to main module structure ([#12671](https://github.com/RocketChat/Rocket.Chat/pull/12671)) -- Convert rocketchat-irc to main module structure ([#12672](https://github.com/RocketChat/Rocket.Chat/pull/12672) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-irc to main module structure ([#12672](https://github.com/RocketChat/Rocket.Chat/pull/12672)) -- Convert rocketchat-issuelinks to main module structure ([#12674](https://github.com/RocketChat/Rocket.Chat/pull/12674) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-issuelinks to main module structure ([#12674](https://github.com/RocketChat/Rocket.Chat/pull/12674)) -- Convert rocketchat-katex to main module structure ([#12895](https://github.com/RocketChat/Rocket.Chat/pull/12895) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-katex to main module structure ([#12895](https://github.com/RocketChat/Rocket.Chat/pull/12895)) -- Convert rocketchat-ldap to main module structure ([#12678](https://github.com/RocketChat/Rocket.Chat/pull/12678) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-ldap to main module structure ([#12678](https://github.com/RocketChat/Rocket.Chat/pull/12678)) -- Convert rocketchat-livechat to main module structure ([#12942](https://github.com/RocketChat/Rocket.Chat/pull/12942) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-livechat to main module structure ([#12942](https://github.com/RocketChat/Rocket.Chat/pull/12942)) -- Convert rocketchat-logger to main module structure and remove Logger from eslintrc ([#12995](https://github.com/RocketChat/Rocket.Chat/pull/12995) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-logger to main module structure and remove Logger from eslintrc ([#12995](https://github.com/RocketChat/Rocket.Chat/pull/12995)) -- Convert rocketchat-mail-messages to main module structure ([#12682](https://github.com/RocketChat/Rocket.Chat/pull/12682) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-mail-messages to main module structure ([#12682](https://github.com/RocketChat/Rocket.Chat/pull/12682)) -- Convert rocketchat-mapview to main module structure ([#12701](https://github.com/RocketChat/Rocket.Chat/pull/12701) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-mapview to main module structure ([#12701](https://github.com/RocketChat/Rocket.Chat/pull/12701)) -- Convert rocketchat-markdown to main module structure ([#12755](https://github.com/RocketChat/Rocket.Chat/pull/12755) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-markdown to main module structure ([#12755](https://github.com/RocketChat/Rocket.Chat/pull/12755)) -- Convert rocketchat-mentions to main module structure ([#12756](https://github.com/RocketChat/Rocket.Chat/pull/12756) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-mentions to main module structure ([#12756](https://github.com/RocketChat/Rocket.Chat/pull/12756)) -- Convert rocketchat-message-action to main module structure ([#12759](https://github.com/RocketChat/Rocket.Chat/pull/12759) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-message-action to main module structure ([#12759](https://github.com/RocketChat/Rocket.Chat/pull/12759)) -- Convert rocketchat-message-attachments to main module structure ([#12760](https://github.com/RocketChat/Rocket.Chat/pull/12760) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-message-attachments to main module structure ([#12760](https://github.com/RocketChat/Rocket.Chat/pull/12760)) -- Convert rocketchat-message-mark-as-unread to main module structure ([#12766](https://github.com/RocketChat/Rocket.Chat/pull/12766) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-message-mark-as-unread to main module structure ([#12766](https://github.com/RocketChat/Rocket.Chat/pull/12766)) -- Convert rocketchat-message-pin to main module structure ([#12767](https://github.com/RocketChat/Rocket.Chat/pull/12767) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-message-pin to main module structure ([#12767](https://github.com/RocketChat/Rocket.Chat/pull/12767)) -- Convert rocketchat-message-snippet to main module structure ([#12768](https://github.com/RocketChat/Rocket.Chat/pull/12768) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-message-snippet to main module structure ([#12768](https://github.com/RocketChat/Rocket.Chat/pull/12768)) -- Convert rocketchat-message-star to main module structure ([#12770](https://github.com/RocketChat/Rocket.Chat/pull/12770) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-message-star to main module structure ([#12770](https://github.com/RocketChat/Rocket.Chat/pull/12770)) -- Convert rocketchat-migrations to main-module structure ([#12772](https://github.com/RocketChat/Rocket.Chat/pull/12772) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-migrations to main-module structure ([#12772](https://github.com/RocketChat/Rocket.Chat/pull/12772)) -- Convert rocketchat-oauth2-server-config to main module structure ([#12773](https://github.com/RocketChat/Rocket.Chat/pull/12773) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-oauth2-server-config to main module structure ([#12773](https://github.com/RocketChat/Rocket.Chat/pull/12773)) -- Convert rocketchat-oembed to main module structure ([#12775](https://github.com/RocketChat/Rocket.Chat/pull/12775) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-oembed to main module structure ([#12775](https://github.com/RocketChat/Rocket.Chat/pull/12775)) -- Convert rocketchat-otr to main module structure ([#12777](https://github.com/RocketChat/Rocket.Chat/pull/12777) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-otr to main module structure ([#12777](https://github.com/RocketChat/Rocket.Chat/pull/12777)) -- Convert rocketchat-push-notifications to main module structure ([#12778](https://github.com/RocketChat/Rocket.Chat/pull/12778) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-push-notifications to main module structure ([#12778](https://github.com/RocketChat/Rocket.Chat/pull/12778)) -- Convert rocketchat-retention-policy to main module structure ([#12797](https://github.com/RocketChat/Rocket.Chat/pull/12797) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-retention-policy to main module structure ([#12797](https://github.com/RocketChat/Rocket.Chat/pull/12797)) -- Convert rocketchat-sandstorm to main module structure ([#12799](https://github.com/RocketChat/Rocket.Chat/pull/12799) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-sandstorm to main module structure ([#12799](https://github.com/RocketChat/Rocket.Chat/pull/12799)) -- Convert rocketchat-search to main module structure ([#12801](https://github.com/RocketChat/Rocket.Chat/pull/12801) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-search to main module structure ([#12801](https://github.com/RocketChat/Rocket.Chat/pull/12801)) -- Convert rocketchat-setup-wizard to main module structure ([#12806](https://github.com/RocketChat/Rocket.Chat/pull/12806) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-setup-wizard to main module structure ([#12806](https://github.com/RocketChat/Rocket.Chat/pull/12806)) -- Convert rocketchat-slackbridge to main module structure ([#12807](https://github.com/RocketChat/Rocket.Chat/pull/12807) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-slackbridge to main module structure ([#12807](https://github.com/RocketChat/Rocket.Chat/pull/12807)) -- Convert rocketchat-slashcomands-archiveroom to main module structure ([#12810](https://github.com/RocketChat/Rocket.Chat/pull/12810) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-slashcomands-archiveroom to main module structure ([#12810](https://github.com/RocketChat/Rocket.Chat/pull/12810)) -- Convert rocketchat-slashcommands-asciiarts to main module structure ([#12808](https://github.com/RocketChat/Rocket.Chat/pull/12808) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-slashcommands-asciiarts to main module structure ([#12808](https://github.com/RocketChat/Rocket.Chat/pull/12808)) -- Convert rocketchat-slashcommands-create to main module structure ([#12811](https://github.com/RocketChat/Rocket.Chat/pull/12811) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-slashcommands-create to main module structure ([#12811](https://github.com/RocketChat/Rocket.Chat/pull/12811)) -- Convert rocketchat-slashcommands-help to main module structure ([#12812](https://github.com/RocketChat/Rocket.Chat/pull/12812) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-slashcommands-help to main module structure ([#12812](https://github.com/RocketChat/Rocket.Chat/pull/12812)) -- Convert rocketchat-slashcommands-hide to main module structure ([#12813](https://github.com/RocketChat/Rocket.Chat/pull/12813) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-slashcommands-hide to main module structure ([#12813](https://github.com/RocketChat/Rocket.Chat/pull/12813)) -- Convert rocketchat-slashcommands-invite to main module structure ([#12814](https://github.com/RocketChat/Rocket.Chat/pull/12814) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-slashcommands-invite to main module structure ([#12814](https://github.com/RocketChat/Rocket.Chat/pull/12814)) -- Convert rocketchat-slashcommands-inviteall to main module structure ([#12815](https://github.com/RocketChat/Rocket.Chat/pull/12815) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-slashcommands-inviteall to main module structure ([#12815](https://github.com/RocketChat/Rocket.Chat/pull/12815)) -- Convert rocketchat-slashcommands-join to main module structure ([#12816](https://github.com/RocketChat/Rocket.Chat/pull/12816) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-slashcommands-join to main module structure ([#12816](https://github.com/RocketChat/Rocket.Chat/pull/12816)) -- Convert rocketchat-slashcommands-kick to main module structure ([#12817](https://github.com/RocketChat/Rocket.Chat/pull/12817) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-slashcommands-kick to main module structure ([#12817](https://github.com/RocketChat/Rocket.Chat/pull/12817)) -- Convert rocketchat-slashcommands-leave to main module structure ([#12821](https://github.com/RocketChat/Rocket.Chat/pull/12821) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-slashcommands-leave to main module structure ([#12821](https://github.com/RocketChat/Rocket.Chat/pull/12821)) -- Convert rocketchat-slashcommands-me to main module structure ([#12822](https://github.com/RocketChat/Rocket.Chat/pull/12822) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-slashcommands-me to main module structure ([#12822](https://github.com/RocketChat/Rocket.Chat/pull/12822)) -- Convert rocketchat-slashcommands-msg to main module structure ([#12823](https://github.com/RocketChat/Rocket.Chat/pull/12823) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-slashcommands-msg to main module structure ([#12823](https://github.com/RocketChat/Rocket.Chat/pull/12823)) -- Convert rocketchat-slashcommands-mute to main module structure ([#12824](https://github.com/RocketChat/Rocket.Chat/pull/12824) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-slashcommands-mute to main module structure ([#12824](https://github.com/RocketChat/Rocket.Chat/pull/12824)) -- Convert rocketchat-slashcommands-open to main module structure ([#12825](https://github.com/RocketChat/Rocket.Chat/pull/12825) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-slashcommands-open to main module structure ([#12825](https://github.com/RocketChat/Rocket.Chat/pull/12825)) -- Convert rocketchat-slashcommands-topic to main module structure ([#12826](https://github.com/RocketChat/Rocket.Chat/pull/12826) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-slashcommands-topic to main module structure ([#12826](https://github.com/RocketChat/Rocket.Chat/pull/12826)) -- Convert rocketchat-slashcommands-unarchiveroom to main module structure ([#12827](https://github.com/RocketChat/Rocket.Chat/pull/12827) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-slashcommands-unarchiveroom to main module structure ([#12827](https://github.com/RocketChat/Rocket.Chat/pull/12827)) -- Convert rocketchat-slider to main module structure ([#12828](https://github.com/RocketChat/Rocket.Chat/pull/12828) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-slider to main module structure ([#12828](https://github.com/RocketChat/Rocket.Chat/pull/12828)) -- Convert rocketchat-smarsh-connector to main module structure ([#12830](https://github.com/RocketChat/Rocket.Chat/pull/12830) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-smarsh-connector to main module structure ([#12830](https://github.com/RocketChat/Rocket.Chat/pull/12830)) -- Convert rocketchat-sms to main module structure ([#12831](https://github.com/RocketChat/Rocket.Chat/pull/12831) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-sms to main module structure ([#12831](https://github.com/RocketChat/Rocket.Chat/pull/12831)) -- Convert rocketchat-spotify to main module structure ([#12832](https://github.com/RocketChat/Rocket.Chat/pull/12832) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-spotify to main module structure ([#12832](https://github.com/RocketChat/Rocket.Chat/pull/12832)) -- Convert rocketchat-statistics to main module structure ([#12833](https://github.com/RocketChat/Rocket.Chat/pull/12833) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-statistics to main module structure ([#12833](https://github.com/RocketChat/Rocket.Chat/pull/12833)) -- Convert rocketchat-theme to main module structure ([#12896](https://github.com/RocketChat/Rocket.Chat/pull/12896) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-theme to main module structure ([#12896](https://github.com/RocketChat/Rocket.Chat/pull/12896)) -- Convert rocketchat-token-login to main module structure ([#12837](https://github.com/RocketChat/Rocket.Chat/pull/12837) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-token-login to main module structure ([#12837](https://github.com/RocketChat/Rocket.Chat/pull/12837)) -- Convert rocketchat-tokenpass to main module structure ([#12838](https://github.com/RocketChat/Rocket.Chat/pull/12838) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-tokenpass to main module structure ([#12838](https://github.com/RocketChat/Rocket.Chat/pull/12838)) -- Convert rocketchat-tooltip to main module structure ([#12839](https://github.com/RocketChat/Rocket.Chat/pull/12839) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-tooltip to main module structure ([#12839](https://github.com/RocketChat/Rocket.Chat/pull/12839)) -- Convert rocketchat-ui-admin to main module structure ([#12844](https://github.com/RocketChat/Rocket.Chat/pull/12844) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-ui-admin to main module structure ([#12844](https://github.com/RocketChat/Rocket.Chat/pull/12844)) -- Convert rocketchat-ui-clean-history to main module structure ([#12846](https://github.com/RocketChat/Rocket.Chat/pull/12846) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-ui-clean-history to main module structure ([#12846](https://github.com/RocketChat/Rocket.Chat/pull/12846)) -- Convert rocketchat-ui-login to main module structure ([#12861](https://github.com/RocketChat/Rocket.Chat/pull/12861) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-ui-login to main module structure ([#12861](https://github.com/RocketChat/Rocket.Chat/pull/12861)) -- Convert rocketchat-ui-message to main module structure ([#12871](https://github.com/RocketChat/Rocket.Chat/pull/12871) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-ui-message to main module structure ([#12871](https://github.com/RocketChat/Rocket.Chat/pull/12871)) -- Convert rocketchat-ui-vrecord to main module structure ([#12875](https://github.com/RocketChat/Rocket.Chat/pull/12875) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-ui-vrecord to main module structure ([#12875](https://github.com/RocketChat/Rocket.Chat/pull/12875)) -- Convert rocketchat-user-data-dowload to main module structure ([#12877](https://github.com/RocketChat/Rocket.Chat/pull/12877) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-user-data-dowload to main module structure ([#12877](https://github.com/RocketChat/Rocket.Chat/pull/12877)) -- Convert rocketchat-version-check to main module structure ([#12879](https://github.com/RocketChat/Rocket.Chat/pull/12879) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-version-check to main module structure ([#12879](https://github.com/RocketChat/Rocket.Chat/pull/12879)) -- Convert rocketchat-videobridge to main module structure ([#12881](https://github.com/RocketChat/Rocket.Chat/pull/12881) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-videobridge to main module structure ([#12881](https://github.com/RocketChat/Rocket.Chat/pull/12881)) -- Convert rocketchat-webdav to main module structure ([#12886](https://github.com/RocketChat/Rocket.Chat/pull/12886) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-webdav to main module structure ([#12886](https://github.com/RocketChat/Rocket.Chat/pull/12886)) -- Convert rocketchat-wordpress to main module structure ([#12887](https://github.com/RocketChat/Rocket.Chat/pull/12887) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-wordpress to main module structure ([#12887](https://github.com/RocketChat/Rocket.Chat/pull/12887)) - Dependencies update ([#12624](https://github.com/RocketChat/Rocket.Chat/pull/12624)) @@ -15720,15 +16923,15 @@ - Fix some Ukrainian translations ([#12712](https://github.com/RocketChat/Rocket.Chat/pull/12712) by [@zdumitru](https://github.com/zdumitru)) -- Fix users.setAvatar endpoint tests and logic ([#12625](https://github.com/RocketChat/Rocket.Chat/pull/12625) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Fix users.setAvatar endpoint tests and logic ([#12625](https://github.com/RocketChat/Rocket.Chat/pull/12625)) -- Fix: Add email dependency in package.js ([#12645](https://github.com/RocketChat/Rocket.Chat/pull/12645) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Fix: Add email dependency in package.js ([#12645](https://github.com/RocketChat/Rocket.Chat/pull/12645)) - Fix: Developers not being able to debug root files in VSCode ([#12440](https://github.com/RocketChat/Rocket.Chat/pull/12440) by [@mrsimpson](https://github.com/mrsimpson)) -- Fix: Exception when registering a user with gravatar ([#12699](https://github.com/RocketChat/Rocket.Chat/pull/12699) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Fix: Exception when registering a user with gravatar ([#12699](https://github.com/RocketChat/Rocket.Chat/pull/12699)) -- Fix: Fix tests by increasing window size ([#12707](https://github.com/RocketChat/Rocket.Chat/pull/12707) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Fix: Fix tests by increasing window size ([#12707](https://github.com/RocketChat/Rocket.Chat/pull/12707)) - Fix: snap push from ci ([#12883](https://github.com/RocketChat/Rocket.Chat/pull/12883)) @@ -15750,9 +16953,9 @@ - Move globals of test to a specific eslintrc file ([#12959](https://github.com/RocketChat/Rocket.Chat/pull/12959)) -- Move isFirefox and isChrome functions to rocketchat-utils ([#13011](https://github.com/RocketChat/Rocket.Chat/pull/13011) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Move isFirefox and isChrome functions to rocketchat-utils ([#13011](https://github.com/RocketChat/Rocket.Chat/pull/13011)) -- Move tapi18n t and isRtl functions from ui to utils ([#13005](https://github.com/RocketChat/Rocket.Chat/pull/13005) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Move tapi18n t and isRtl functions from ui to utils ([#13005](https://github.com/RocketChat/Rocket.Chat/pull/13005)) - Regression: Account pages layout ([#12735](https://github.com/RocketChat/Rocket.Chat/pull/12735)) @@ -15762,7 +16965,7 @@ - Regression: Inherit font-family for message box ([#12729](https://github.com/RocketChat/Rocket.Chat/pull/12729)) -- Regression: List of custom emojis wasn't working ([#13031](https://github.com/RocketChat/Rocket.Chat/pull/13031) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Regression: List of custom emojis wasn't working ([#13031](https://github.com/RocketChat/Rocket.Chat/pull/13031)) - Release 0.72.2 ([#12901](https://github.com/RocketChat/Rocket.Chat/pull/12901)) @@ -15770,21 +16973,21 @@ - Removal of EJSON, Accounts, Email, HTTP, Random, ReactiveDict, ReactiveVar, SHA256 and WebApp global variables ([#12377](https://github.com/RocketChat/Rocket.Chat/pull/12377)) -- Removal of Match, check, moment, Tracker and Mongo global variables ([#12410](https://github.com/RocketChat/Rocket.Chat/pull/12410) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Removal of Match, check, moment, Tracker and Mongo global variables ([#12410](https://github.com/RocketChat/Rocket.Chat/pull/12410)) - Removal of Meteor global variable ([#12371](https://github.com/RocketChat/Rocket.Chat/pull/12371)) -- Removal of TAPi18n and TAPi18next global variables ([#12467](https://github.com/RocketChat/Rocket.Chat/pull/12467) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Removal of TAPi18n and TAPi18next global variables ([#12467](https://github.com/RocketChat/Rocket.Chat/pull/12467)) -- Removal of Template, Blaze, BlazeLayout, FlowRouter, DDPRateLimiter, Session, UAParser, Promise, Reload and CryptoJS global variables ([#12433](https://github.com/RocketChat/Rocket.Chat/pull/12433) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Removal of Template, Blaze, BlazeLayout, FlowRouter, DDPRateLimiter, Session, UAParser, Promise, Reload and CryptoJS global variables ([#12433](https://github.com/RocketChat/Rocket.Chat/pull/12433)) -- Remove /* globals */ from files wave-1 ([#12984](https://github.com/RocketChat/Rocket.Chat/pull/12984) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove /* globals */ from files wave-1 ([#12984](https://github.com/RocketChat/Rocket.Chat/pull/12984)) -- Remove /* globals */ wave 2 ([#12988](https://github.com/RocketChat/Rocket.Chat/pull/12988) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove /* globals */ wave 2 ([#12988](https://github.com/RocketChat/Rocket.Chat/pull/12988)) -- Remove /* globals */ wave 3 ([#12997](https://github.com/RocketChat/Rocket.Chat/pull/12997) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove /* globals */ wave 3 ([#12997](https://github.com/RocketChat/Rocket.Chat/pull/12997)) -- Remove /* globals */ wave 4 ([#12999](https://github.com/RocketChat/Rocket.Chat/pull/12999) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove /* globals */ wave 4 ([#12999](https://github.com/RocketChat/Rocket.Chat/pull/12999)) - Remove conventional changelog cli, we are using our own cli now (Houston) ([#12798](https://github.com/RocketChat/Rocket.Chat/pull/12798)) @@ -15792,13 +16995,13 @@ - Remove global toastr ([#12961](https://github.com/RocketChat/Rocket.Chat/pull/12961)) -- Remove rocketchat-tutum package ([#12840](https://github.com/RocketChat/Rocket.Chat/pull/12840) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove rocketchat-tutum package ([#12840](https://github.com/RocketChat/Rocket.Chat/pull/12840)) - Remove template for feature requests as issues ([#12426](https://github.com/RocketChat/Rocket.Chat/pull/12426)) -- Removed RocketChatFile from globals ([#12650](https://github.com/RocketChat/Rocket.Chat/pull/12650) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Removed RocketChatFile from globals ([#12650](https://github.com/RocketChat/Rocket.Chat/pull/12650)) -- Revert imports of css, reAdd them to the addFiles function ([#12934](https://github.com/RocketChat/Rocket.Chat/pull/12934) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Revert imports of css, reAdd them to the addFiles function ([#12934](https://github.com/RocketChat/Rocket.Chat/pull/12934)) - Update Apps Engine to 1.3.1 ([#12741](https://github.com/RocketChat/Rocket.Chat/pull/12741)) @@ -15811,7 +17014,6 @@ - [@AndreamApp](https://github.com/AndreamApp) - [@Hudell](https://github.com/Hudell) - [@Ismaw34](https://github.com/Ismaw34) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@alexbartsch](https://github.com/alexbartsch) - [@cardoso](https://github.com/cardoso) - [@cyberb](https://github.com/cyberb) @@ -15841,6 +17043,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 - [@LuluGO](https://github.com/LuluGO) +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@d-gubert](https://github.com/d-gubert) - [@engelgabriel](https://github.com/engelgabriel) - [@geekgonecrazy](https://github.com/geekgonecrazy) @@ -15918,21 +17121,21 @@ - Bump Apps-Engine version ([#12848](https://github.com/RocketChat/Rocket.Chat/pull/12848)) -- Change file order in rocketchat-cors ([#12804](https://github.com/RocketChat/Rocket.Chat/pull/12804) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Change file order in rocketchat-cors ([#12804](https://github.com/RocketChat/Rocket.Chat/pull/12804)) -- Release 0.72.1 ([#12850](https://github.com/RocketChat/Rocket.Chat/pull/12850) by [@Hudell](https://github.com/Hudell) & [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@ohmonster](https://github.com/ohmonster) & [@piotrkochan](https://github.com/piotrkochan)) +- Release 0.72.1 ([#12850](https://github.com/RocketChat/Rocket.Chat/pull/12850) by [@Hudell](https://github.com/Hudell) & [@ohmonster](https://github.com/ohmonster) & [@piotrkochan](https://github.com/piotrkochan))
### 👩‍💻👨‍💻 Contributors 😍 - [@Hudell](https://github.com/Hudell) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@ohmonster](https://github.com/ohmonster) - [@piotrkochan](https://github.com/piotrkochan) ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@d-gubert](https://github.com/d-gubert) - [@rodrigok](https://github.com/rodrigok) - [@sampaiodiego](https://github.com/sampaiodiego) @@ -15953,11 +17156,11 @@ - /api/v1/spotlight: return joinCodeRequired field for rooms ([#12651](https://github.com/RocketChat/Rocket.Chat/pull/12651) by [@cardoso](https://github.com/cardoso)) -- Add permission to enable personal access token to specific roles ([#12309](https://github.com/RocketChat/Rocket.Chat/pull/12309) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add permission to enable personal access token to specific roles ([#12309](https://github.com/RocketChat/Rocket.Chat/pull/12309)) - Make Livechat's widget draggable ([#12378](https://github.com/RocketChat/Rocket.Chat/pull/12378)) -- New API Endpoints for the new version of JS SDK ([#12623](https://github.com/RocketChat/Rocket.Chat/pull/12623) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- New API Endpoints for the new version of JS SDK ([#12623](https://github.com/RocketChat/Rocket.Chat/pull/12623)) - Option to reset e2e key ([#12483](https://github.com/RocketChat/Rocket.Chat/pull/12483) by [@Hudell](https://github.com/Hudell)) @@ -15972,7 +17175,7 @@ - Add new acceptable header for Livechat REST requests ([#12561](https://github.com/RocketChat/Rocket.Chat/pull/12561)) -- Add rooms property in user object, if the user has the permission, with rooms roles ([#12105](https://github.com/RocketChat/Rocket.Chat/pull/12105) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add rooms property in user object, if the user has the permission, with rooms roles ([#12105](https://github.com/RocketChat/Rocket.Chat/pull/12105)) - Allow apps to update persistence by association ([#12714](https://github.com/RocketChat/Rocket.Chat/pull/12714)) @@ -15990,7 +17193,7 @@ - Ignore non-existent Livechat custom fields on Livechat API ([#12522](https://github.com/RocketChat/Rocket.Chat/pull/12522)) -- Improve unreads and unreadsFrom response, prevent it to be equal null ([#12563](https://github.com/RocketChat/Rocket.Chat/pull/12563) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Improve unreads and unreadsFrom response, prevent it to be equal null ([#12563](https://github.com/RocketChat/Rocket.Chat/pull/12563)) - Japanese translations ([#12382](https://github.com/RocketChat/Rocket.Chat/pull/12382) by [@ura14h](https://github.com/ura14h)) @@ -16019,9 +17222,9 @@ - Emoji picker is not in viewport on small screens ([#12457](https://github.com/RocketChat/Rocket.Chat/pull/12457) by [@ramrami](https://github.com/ramrami)) -- Fix favico error ([#12643](https://github.com/RocketChat/Rocket.Chat/pull/12643) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Fix favico error ([#12643](https://github.com/RocketChat/Rocket.Chat/pull/12643)) -- Fix wrong parameter in chat.delete endpoint and add some test cases ([#12408](https://github.com/RocketChat/Rocket.Chat/pull/12408) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Fix wrong parameter in chat.delete endpoint and add some test cases ([#12408](https://github.com/RocketChat/Rocket.Chat/pull/12408)) - Fixed Anonymous Registration ([#12633](https://github.com/RocketChat/Rocket.Chat/pull/12633) by [@wreiske](https://github.com/wreiske)) @@ -16031,11 +17234,11 @@ - high cpu usage ~ svg icon ([#12677](https://github.com/RocketChat/Rocket.Chat/pull/12677) by [@ph1p](https://github.com/ph1p)) -- Import missed file in rocketchat-authorization ([#12570](https://github.com/RocketChat/Rocket.Chat/pull/12570) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Import missed file in rocketchat-authorization ([#12570](https://github.com/RocketChat/Rocket.Chat/pull/12570)) - Manage own integrations permissions check ([#12397](https://github.com/RocketChat/Rocket.Chat/pull/12397)) -- Prevent subscriptions and calls to rooms events that the user is not participating ([#12558](https://github.com/RocketChat/Rocket.Chat/pull/12558) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Prevent subscriptions and calls to rooms events that the user is not participating ([#12558](https://github.com/RocketChat/Rocket.Chat/pull/12558)) - Spotlight method being called multiple times ([#12536](https://github.com/RocketChat/Rocket.Chat/pull/12536)) @@ -16043,115 +17246,115 @@ - Update caret position on insert a new line in message box ([#12713](https://github.com/RocketChat/Rocket.Chat/pull/12713)) -- Wrong test case for `users.setAvatar` endpoint ([#12539](https://github.com/RocketChat/Rocket.Chat/pull/12539) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Wrong test case for `users.setAvatar` endpoint ([#12539](https://github.com/RocketChat/Rocket.Chat/pull/12539))
🔍 Minor changes -- Convert rocketchat-channel-settings to main module structure ([#12594](https://github.com/RocketChat/Rocket.Chat/pull/12594) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-channel-settings to main module structure ([#12594](https://github.com/RocketChat/Rocket.Chat/pull/12594)) -- Convert rocketchat-emoji-custom to main module structure ([#12604](https://github.com/RocketChat/Rocket.Chat/pull/12604) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-emoji-custom to main module structure ([#12604](https://github.com/RocketChat/Rocket.Chat/pull/12604)) -- Convert rocketchat-importer-slack to main module structure ([#12666](https://github.com/RocketChat/Rocket.Chat/pull/12666) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-importer-slack to main module structure ([#12666](https://github.com/RocketChat/Rocket.Chat/pull/12666)) -- Convert rocketchat-livestream to main module structure ([#12679](https://github.com/RocketChat/Rocket.Chat/pull/12679) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-livestream to main module structure ([#12679](https://github.com/RocketChat/Rocket.Chat/pull/12679)) - [DOCS] Remove Cordova links, include F-Droid download button and few other adjustments ([#12583](https://github.com/RocketChat/Rocket.Chat/pull/12583) by [@rafaelks](https://github.com/rafaelks)) - Added "npm install" to quick start for developers ([#12374](https://github.com/RocketChat/Rocket.Chat/pull/12374) by [@wreiske](https://github.com/wreiske)) -- Added imports for global variables in rocketchat-google-natural-language package ([#12647](https://github.com/RocketChat/Rocket.Chat/pull/12647) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Added imports for global variables in rocketchat-google-natural-language package ([#12647](https://github.com/RocketChat/Rocket.Chat/pull/12647)) - Bump Apps Engine to 1.3.0 ([#12705](https://github.com/RocketChat/Rocket.Chat/pull/12705)) -- Convert chatpal search package to modular structure ([#12485](https://github.com/RocketChat/Rocket.Chat/pull/12485) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert chatpal search package to modular structure ([#12485](https://github.com/RocketChat/Rocket.Chat/pull/12485)) -- Convert emoji-emojione to main module structure ([#12605](https://github.com/RocketChat/Rocket.Chat/pull/12605) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert emoji-emojione to main module structure ([#12605](https://github.com/RocketChat/Rocket.Chat/pull/12605)) -- Convert meteor-accounts-saml to main module structure ([#12486](https://github.com/RocketChat/Rocket.Chat/pull/12486) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert meteor-accounts-saml to main module structure ([#12486](https://github.com/RocketChat/Rocket.Chat/pull/12486)) -- Convert meteor-autocomplete package to main module structure ([#12491](https://github.com/RocketChat/Rocket.Chat/pull/12491) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert meteor-autocomplete package to main module structure ([#12491](https://github.com/RocketChat/Rocket.Chat/pull/12491)) -- Convert meteor-timesync to main module structure ([#12495](https://github.com/RocketChat/Rocket.Chat/pull/12495) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert meteor-timesync to main module structure ([#12495](https://github.com/RocketChat/Rocket.Chat/pull/12495)) -- Convert rocketchat-2fa to main module structure ([#12501](https://github.com/RocketChat/Rocket.Chat/pull/12501) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-2fa to main module structure ([#12501](https://github.com/RocketChat/Rocket.Chat/pull/12501)) -- Convert rocketchat-action-links to main module structure ([#12503](https://github.com/RocketChat/Rocket.Chat/pull/12503) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-action-links to main module structure ([#12503](https://github.com/RocketChat/Rocket.Chat/pull/12503)) -- Convert rocketchat-analytics to main module structure ([#12506](https://github.com/RocketChat/Rocket.Chat/pull/12506) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-analytics to main module structure ([#12506](https://github.com/RocketChat/Rocket.Chat/pull/12506)) -- Convert rocketchat-api to main module structure ([#12510](https://github.com/RocketChat/Rocket.Chat/pull/12510) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-api to main module structure ([#12510](https://github.com/RocketChat/Rocket.Chat/pull/12510)) -- Convert rocketchat-assets to main module structure ([#12521](https://github.com/RocketChat/Rocket.Chat/pull/12521) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-assets to main module structure ([#12521](https://github.com/RocketChat/Rocket.Chat/pull/12521)) -- Convert rocketchat-authorization to main module structure ([#12523](https://github.com/RocketChat/Rocket.Chat/pull/12523) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-authorization to main module structure ([#12523](https://github.com/RocketChat/Rocket.Chat/pull/12523)) -- Convert rocketchat-autolinker to main module structure ([#12529](https://github.com/RocketChat/Rocket.Chat/pull/12529) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-autolinker to main module structure ([#12529](https://github.com/RocketChat/Rocket.Chat/pull/12529)) -- Convert rocketchat-autotranslate to main module structure ([#12530](https://github.com/RocketChat/Rocket.Chat/pull/12530) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-autotranslate to main module structure ([#12530](https://github.com/RocketChat/Rocket.Chat/pull/12530)) -- Convert rocketchat-bot-helpers to main module structure ([#12531](https://github.com/RocketChat/Rocket.Chat/pull/12531) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-bot-helpers to main module structure ([#12531](https://github.com/RocketChat/Rocket.Chat/pull/12531)) -- Convert rocketchat-cas to main module structure ([#12532](https://github.com/RocketChat/Rocket.Chat/pull/12532) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-cas to main module structure ([#12532](https://github.com/RocketChat/Rocket.Chat/pull/12532)) -- Convert rocketchat-channel-settings-mail-messages to main module structure ([#12537](https://github.com/RocketChat/Rocket.Chat/pull/12537) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-channel-settings-mail-messages to main module structure ([#12537](https://github.com/RocketChat/Rocket.Chat/pull/12537)) -- Convert rocketchat-colors to main module structure ([#12538](https://github.com/RocketChat/Rocket.Chat/pull/12538) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-colors to main module structure ([#12538](https://github.com/RocketChat/Rocket.Chat/pull/12538)) -- Convert rocketchat-cors to main module structure ([#12595](https://github.com/RocketChat/Rocket.Chat/pull/12595) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-cors to main module structure ([#12595](https://github.com/RocketChat/Rocket.Chat/pull/12595)) -- Convert rocketchat-crowd to main module structure ([#12596](https://github.com/RocketChat/Rocket.Chat/pull/12596) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-crowd to main module structure ([#12596](https://github.com/RocketChat/Rocket.Chat/pull/12596)) -- Convert rocketchat-custom-sounds to main module structure ([#12599](https://github.com/RocketChat/Rocket.Chat/pull/12599) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-custom-sounds to main module structure ([#12599](https://github.com/RocketChat/Rocket.Chat/pull/12599)) -- Convert rocketchat-dolphin to main module structure ([#12600](https://github.com/RocketChat/Rocket.Chat/pull/12600) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-dolphin to main module structure ([#12600](https://github.com/RocketChat/Rocket.Chat/pull/12600)) -- Convert rocketchat-drupal to main module structure ([#12601](https://github.com/RocketChat/Rocket.Chat/pull/12601) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-drupal to main module structure ([#12601](https://github.com/RocketChat/Rocket.Chat/pull/12601)) -- Convert rocketchat-emoji to main module structure ([#12603](https://github.com/RocketChat/Rocket.Chat/pull/12603) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-emoji to main module structure ([#12603](https://github.com/RocketChat/Rocket.Chat/pull/12603)) -- Convert rocketchat-error-handler to main module structure ([#12606](https://github.com/RocketChat/Rocket.Chat/pull/12606) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-error-handler to main module structure ([#12606](https://github.com/RocketChat/Rocket.Chat/pull/12606)) -- Convert rocketchat-favico to main module structure ([#12607](https://github.com/RocketChat/Rocket.Chat/pull/12607) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-favico to main module structure ([#12607](https://github.com/RocketChat/Rocket.Chat/pull/12607)) -- Convert rocketchat-file to main module structure ([#12644](https://github.com/RocketChat/Rocket.Chat/pull/12644) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-file to main module structure ([#12644](https://github.com/RocketChat/Rocket.Chat/pull/12644)) -- Convert rocketchat-github-enterprise to main module structure ([#12642](https://github.com/RocketChat/Rocket.Chat/pull/12642) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-github-enterprise to main module structure ([#12642](https://github.com/RocketChat/Rocket.Chat/pull/12642)) -- Convert rocketchat-gitlab to main module structure ([#12646](https://github.com/RocketChat/Rocket.Chat/pull/12646) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-gitlab to main module structure ([#12646](https://github.com/RocketChat/Rocket.Chat/pull/12646)) -- Convert rocketchat-google-vision to main module structure ([#12649](https://github.com/RocketChat/Rocket.Chat/pull/12649) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-google-vision to main module structure ([#12649](https://github.com/RocketChat/Rocket.Chat/pull/12649)) -- Convert rocketchat-grant to main module structure ([#12657](https://github.com/RocketChat/Rocket.Chat/pull/12657) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-grant to main module structure ([#12657](https://github.com/RocketChat/Rocket.Chat/pull/12657)) -- Convert rocketchat-graphql to main module structure ([#12658](https://github.com/RocketChat/Rocket.Chat/pull/12658) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-graphql to main module structure ([#12658](https://github.com/RocketChat/Rocket.Chat/pull/12658)) -- Convert rocketchat-highlight-words to main module structure ([#12659](https://github.com/RocketChat/Rocket.Chat/pull/12659) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-highlight-words to main module structure ([#12659](https://github.com/RocketChat/Rocket.Chat/pull/12659)) -- Convert rocketchat-iframe-login to main module structure ([#12661](https://github.com/RocketChat/Rocket.Chat/pull/12661) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-iframe-login to main module structure ([#12661](https://github.com/RocketChat/Rocket.Chat/pull/12661)) -- Convert rocketchat-importer to main module structure ([#12662](https://github.com/RocketChat/Rocket.Chat/pull/12662) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-importer to main module structure ([#12662](https://github.com/RocketChat/Rocket.Chat/pull/12662)) -- Convert rocketchat-importer-csv to main module structure ([#12663](https://github.com/RocketChat/Rocket.Chat/pull/12663) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-importer-csv to main module structure ([#12663](https://github.com/RocketChat/Rocket.Chat/pull/12663)) -- Convert rocketchat-importer-hipchat to main module structure ([#12664](https://github.com/RocketChat/Rocket.Chat/pull/12664) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-importer-hipchat to main module structure ([#12664](https://github.com/RocketChat/Rocket.Chat/pull/12664)) -- Convert rocketchat-importer-hipchat-enterprise to main module structure ([#12665](https://github.com/RocketChat/Rocket.Chat/pull/12665) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-importer-hipchat-enterprise to main module structure ([#12665](https://github.com/RocketChat/Rocket.Chat/pull/12665)) -- Convert rocketchat-importer-slack-users to main module structure ([#12669](https://github.com/RocketChat/Rocket.Chat/pull/12669) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-importer-slack-users to main module structure ([#12669](https://github.com/RocketChat/Rocket.Chat/pull/12669)) -- Convert rocketchat-integrations to main module structure ([#12670](https://github.com/RocketChat/Rocket.Chat/pull/12670) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-integrations to main module structure ([#12670](https://github.com/RocketChat/Rocket.Chat/pull/12670)) -- Convert rocketchat-internal-hubot to main module structure ([#12671](https://github.com/RocketChat/Rocket.Chat/pull/12671) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-internal-hubot to main module structure ([#12671](https://github.com/RocketChat/Rocket.Chat/pull/12671)) -- Convert rocketchat-irc to main module structure ([#12672](https://github.com/RocketChat/Rocket.Chat/pull/12672) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-irc to main module structure ([#12672](https://github.com/RocketChat/Rocket.Chat/pull/12672)) -- Convert rocketchat-issuelinks to main module structure ([#12674](https://github.com/RocketChat/Rocket.Chat/pull/12674) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-issuelinks to main module structure ([#12674](https://github.com/RocketChat/Rocket.Chat/pull/12674)) -- Convert rocketchat-ldap to main module structure ([#12678](https://github.com/RocketChat/Rocket.Chat/pull/12678) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-ldap to main module structure ([#12678](https://github.com/RocketChat/Rocket.Chat/pull/12678)) -- Convert rocketchat-mail-messages to main module structure ([#12682](https://github.com/RocketChat/Rocket.Chat/pull/12682) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Convert rocketchat-mail-messages to main module structure ([#12682](https://github.com/RocketChat/Rocket.Chat/pull/12682)) - Fix crowd error with import of SyncedCron ([#12641](https://github.com/RocketChat/Rocket.Chat/pull/12641)) @@ -16163,15 +17366,15 @@ - Fix some Ukrainian translations ([#12712](https://github.com/RocketChat/Rocket.Chat/pull/12712) by [@zdumitru](https://github.com/zdumitru)) -- Fix users.setAvatar endpoint tests and logic ([#12625](https://github.com/RocketChat/Rocket.Chat/pull/12625) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Fix users.setAvatar endpoint tests and logic ([#12625](https://github.com/RocketChat/Rocket.Chat/pull/12625)) -- Fix: Add email dependency in package.js ([#12645](https://github.com/RocketChat/Rocket.Chat/pull/12645) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Fix: Add email dependency in package.js ([#12645](https://github.com/RocketChat/Rocket.Chat/pull/12645)) - Fix: Developers not being able to debug root files in VSCode ([#12440](https://github.com/RocketChat/Rocket.Chat/pull/12440) by [@mrsimpson](https://github.com/mrsimpson)) -- Fix: Exception when registering a user with gravatar ([#12699](https://github.com/RocketChat/Rocket.Chat/pull/12699) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Fix: Exception when registering a user with gravatar ([#12699](https://github.com/RocketChat/Rocket.Chat/pull/12699)) -- Fix: Fix tests by increasing window size ([#12707](https://github.com/RocketChat/Rocket.Chat/pull/12707) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Fix: Fix tests by increasing window size ([#12707](https://github.com/RocketChat/Rocket.Chat/pull/12707)) - Improve: Add missing translation keys. ([#12708](https://github.com/RocketChat/Rocket.Chat/pull/12708) by [@ura14h](https://github.com/ura14h)) @@ -16191,17 +17394,17 @@ - Removal of EJSON, Accounts, Email, HTTP, Random, ReactiveDict, ReactiveVar, SHA256 and WebApp global variables ([#12377](https://github.com/RocketChat/Rocket.Chat/pull/12377)) -- Removal of Match, check, moment, Tracker and Mongo global variables ([#12410](https://github.com/RocketChat/Rocket.Chat/pull/12410) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Removal of Match, check, moment, Tracker and Mongo global variables ([#12410](https://github.com/RocketChat/Rocket.Chat/pull/12410)) - Removal of Meteor global variable ([#12371](https://github.com/RocketChat/Rocket.Chat/pull/12371)) -- Removal of TAPi18n and TAPi18next global variables ([#12467](https://github.com/RocketChat/Rocket.Chat/pull/12467) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Removal of TAPi18n and TAPi18next global variables ([#12467](https://github.com/RocketChat/Rocket.Chat/pull/12467)) -- Removal of Template, Blaze, BlazeLayout, FlowRouter, DDPRateLimiter, Session, UAParser, Promise, Reload and CryptoJS global variables ([#12433](https://github.com/RocketChat/Rocket.Chat/pull/12433) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Removal of Template, Blaze, BlazeLayout, FlowRouter, DDPRateLimiter, Session, UAParser, Promise, Reload and CryptoJS global variables ([#12433](https://github.com/RocketChat/Rocket.Chat/pull/12433)) - Remove template for feature requests as issues ([#12426](https://github.com/RocketChat/Rocket.Chat/pull/12426)) -- Removed RocketChatFile from globals ([#12650](https://github.com/RocketChat/Rocket.Chat/pull/12650) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Removed RocketChatFile from globals ([#12650](https://github.com/RocketChat/Rocket.Chat/pull/12650)) - Update Apps Engine to 1.3.1 ([#12741](https://github.com/RocketChat/Rocket.Chat/pull/12741)) @@ -16214,7 +17417,6 @@ - [@AndreamApp](https://github.com/AndreamApp) - [@Hudell](https://github.com/Hudell) - [@Ismaw34](https://github.com/Ismaw34) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@cardoso](https://github.com/cardoso) - [@imronras](https://github.com/imronras) - [@karlprieb](https://github.com/karlprieb) @@ -16232,6 +17434,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@engelgabriel](https://github.com/engelgabriel) - [@ggazzo](https://github.com/ggazzo) - [@marceloschmidt](https://github.com/marceloschmidt) @@ -16290,9 +17493,9 @@ ### ⚠️ BREAKING CHANGES -- Add expiration to API login tokens and fix duplicate login tokens created by LDAP ([#12186](https://github.com/RocketChat/Rocket.Chat/pull/12186) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add expiration to API login tokens and fix duplicate login tokens created by LDAP ([#12186](https://github.com/RocketChat/Rocket.Chat/pull/12186)) -- Update `lastMessage` rooms property and convert the "starred" property, to the same format ([#12266](https://github.com/RocketChat/Rocket.Chat/pull/12266) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Update `lastMessage` rooms property and convert the "starred" property, to the same format ([#12266](https://github.com/RocketChat/Rocket.Chat/pull/12266)) ### 🎉 New features @@ -16301,7 +17504,7 @@ - Add "help wanted" section to Readme ([#12432](https://github.com/RocketChat/Rocket.Chat/pull/12432) by [@isabellarussell](https://github.com/isabellarussell)) -- Add delete channel mutation to GraphQL API ([#11860](https://github.com/RocketChat/Rocket.Chat/pull/11860) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add delete channel mutation to GraphQL API ([#11860](https://github.com/RocketChat/Rocket.Chat/pull/11860)) - PDF message attachment preview (client side rendering) ([#10519](https://github.com/RocketChat/Rocket.Chat/pull/10519) by [@kb0304](https://github.com/kb0304)) @@ -16361,13 +17564,13 @@ - Modal confirm on enter ([#12283](https://github.com/RocketChat/Rocket.Chat/pull/12283)) -- Remove e2e from users endpoint responses ([#12344](https://github.com/RocketChat/Rocket.Chat/pull/12344) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove e2e from users endpoint responses ([#12344](https://github.com/RocketChat/Rocket.Chat/pull/12344)) -- REST `users.setAvatar` endpoint wasn't allowing update the avatar of other users even with correct permissions ([#11431](https://github.com/RocketChat/Rocket.Chat/pull/11431) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- REST `users.setAvatar` endpoint wasn't allowing update the avatar of other users even with correct permissions ([#11431](https://github.com/RocketChat/Rocket.Chat/pull/11431)) - Slack importer: image previews not showing ([#11875](https://github.com/RocketChat/Rocket.Chat/pull/11875) by [@Hudell](https://github.com/Hudell) & [@madguy02](https://github.com/madguy02)) -- users.register endpoint to not create an user if username already being used ([#12297](https://github.com/RocketChat/Rocket.Chat/pull/12297) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- users.register endpoint to not create an user if username already being used ([#12297](https://github.com/RocketChat/Rocket.Chat/pull/12297))
🔍 Minor changes @@ -16383,7 +17586,7 @@ - Improve: Drop database between running tests on CI ([#12358](https://github.com/RocketChat/Rocket.Chat/pull/12358)) -- Regression: Change `starred` message property from object to array ([#12405](https://github.com/RocketChat/Rocket.Chat/pull/12405) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Regression: Change `starred` message property from object to array ([#12405](https://github.com/RocketChat/Rocket.Chat/pull/12405)) - Regression: do not render pdf preview on safari <= 12 ([#12375](https://github.com/RocketChat/Rocket.Chat/pull/12375)) @@ -16397,7 +17600,6 @@ - [@Hudell](https://github.com/Hudell) - [@MarcosEllys](https://github.com/MarcosEllys) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@crazy-max](https://github.com/crazy-max) - [@isabellarussell](https://github.com/isabellarussell) - [@kb0304](https://github.com/kb0304) @@ -16410,6 +17612,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@Sing-Li](https://github.com/Sing-Li) - [@geekgonecrazy](https://github.com/geekgonecrazy) - [@ggazzo](https://github.com/ggazzo) @@ -16591,11 +17794,11 @@ - Livechat trigger option to run only once ([#12068](https://github.com/RocketChat/Rocket.Chat/pull/12068) by [@edzluhan](https://github.com/edzluhan)) -- REST endpoint to set groups' announcement ([#11905](https://github.com/RocketChat/Rocket.Chat/pull/11905) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- REST endpoint to set groups' announcement ([#11905](https://github.com/RocketChat/Rocket.Chat/pull/11905)) - REST endpoints to create roles and assign roles to users ([#11855](https://github.com/RocketChat/Rocket.Chat/pull/11855) by [@aferreira44](https://github.com/aferreira44)) -- REST endpoints to get moderators from groups and channels ([#11909](https://github.com/RocketChat/Rocket.Chat/pull/11909) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- REST endpoints to get moderators from groups and channels ([#11909](https://github.com/RocketChat/Rocket.Chat/pull/11909)) - Support for end to end encryption ([#10094](https://github.com/RocketChat/Rocket.Chat/pull/10094) by [@mrinaldhar](https://github.com/mrinaldhar)) @@ -16653,7 +17856,7 @@ - Horizontal scroll on user info tab ([#12102](https://github.com/RocketChat/Rocket.Chat/pull/12102) by [@rssilva](https://github.com/rssilva)) -- Internal error when cross-origin with CORS is disabled ([#11953](https://github.com/RocketChat/Rocket.Chat/pull/11953) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Internal error when cross-origin with CORS is disabled ([#11953](https://github.com/RocketChat/Rocket.Chat/pull/11953)) - IRC Federation no longer working ([#11906](https://github.com/RocketChat/Rocket.Chat/pull/11906) by [@Hudell](https://github.com/Hudell)) @@ -16663,7 +17866,7 @@ - Markdown ampersand escape on links ([#12140](https://github.com/RocketChat/Rocket.Chat/pull/12140) by [@rssilva](https://github.com/rssilva)) -- Message reaction in GraphQL API ([#11967](https://github.com/RocketChat/Rocket.Chat/pull/11967) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Message reaction in GraphQL API ([#11967](https://github.com/RocketChat/Rocket.Chat/pull/11967)) - Not able to set per-channel retention policies if no global policy is set for this channel type ([#11927](https://github.com/RocketChat/Rocket.Chat/pull/11927) by [@vynmera](https://github.com/vynmera)) @@ -16719,7 +17922,7 @@ - LingoHub based on develop ([#11936](https://github.com/RocketChat/Rocket.Chat/pull/11936)) -- Merge master into develop & Set version to 0.70.0-develop ([#11921](https://github.com/RocketChat/Rocket.Chat/pull/11921) by [@Hudell](https://github.com/Hudell) & [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@c0dzilla](https://github.com/c0dzilla) & [@rndmh3ro](https://github.com/rndmh3ro) & [@ubarsaiyan](https://github.com/ubarsaiyan) & [@vynmera](https://github.com/vynmera)) +- Merge master into develop & Set version to 0.70.0-develop ([#11921](https://github.com/RocketChat/Rocket.Chat/pull/11921) by [@Hudell](https://github.com/Hudell) & [@c0dzilla](https://github.com/c0dzilla) & [@rndmh3ro](https://github.com/rndmh3ro) & [@ubarsaiyan](https://github.com/ubarsaiyan) & [@vynmera](https://github.com/vynmera)) - New: Option to change E2E key ([#12169](https://github.com/RocketChat/Rocket.Chat/pull/12169) by [@Hudell](https://github.com/Hudell)) @@ -16733,7 +17936,6 @@ - [@Hudell](https://github.com/Hudell) - [@MIKI785](https://github.com/MIKI785) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@TobiasKappe](https://github.com/TobiasKappe) - [@aferreira44](https://github.com/aferreira44) - [@arch119](https://github.com/arch119) @@ -16757,6 +17959,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@MartinSchoeler](https://github.com/MartinSchoeler) - [@engelgabriel](https://github.com/engelgabriel) - [@geekgonecrazy](https://github.com/geekgonecrazy) @@ -16844,9 +18047,9 @@ - Make font of unread items bolder for better contrast ([#8602](https://github.com/RocketChat/Rocket.Chat/pull/8602) by [@ausminternet](https://github.com/ausminternet)) -- Personal access tokens for users to create API tokens ([#11638](https://github.com/RocketChat/Rocket.Chat/pull/11638) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Personal access tokens for users to create API tokens ([#11638](https://github.com/RocketChat/Rocket.Chat/pull/11638)) -- REST endpoint to manage server assets ([#11697](https://github.com/RocketChat/Rocket.Chat/pull/11697) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- REST endpoint to manage server assets ([#11697](https://github.com/RocketChat/Rocket.Chat/pull/11697)) - Rich message text and image buttons ([#11473](https://github.com/RocketChat/Rocket.Chat/pull/11473) by [@ubarsaiyan](https://github.com/ubarsaiyan)) @@ -16898,7 +18101,7 @@ - Default server language not being applied ([#11719](https://github.com/RocketChat/Rocket.Chat/pull/11719)) -- Delete removed user's subscriptions ([#10700](https://github.com/RocketChat/Rocket.Chat/pull/10700) by [@Hudell](https://github.com/Hudell) & [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Delete removed user's subscriptions ([#10700](https://github.com/RocketChat/Rocket.Chat/pull/10700) by [@Hudell](https://github.com/Hudell)) - directory search table not clickable lines ([#11809](https://github.com/RocketChat/Rocket.Chat/pull/11809)) @@ -16946,11 +18149,11 @@ - Render Attachment Pretext When Markdown Specified ([#11578](https://github.com/RocketChat/Rocket.Chat/pull/11578) by [@glstewart17](https://github.com/glstewart17)) -- REST `im.members` endpoint not working without sort parameter ([#11821](https://github.com/RocketChat/Rocket.Chat/pull/11821) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- REST `im.members` endpoint not working without sort parameter ([#11821](https://github.com/RocketChat/Rocket.Chat/pull/11821)) -- REST endpoints to update user not respecting some settings ([#11474](https://github.com/RocketChat/Rocket.Chat/pull/11474) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- REST endpoints to update user not respecting some settings ([#11474](https://github.com/RocketChat/Rocket.Chat/pull/11474)) -- Results pagination on /directory REST endpoint ([#11551](https://github.com/RocketChat/Rocket.Chat/pull/11551) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Results pagination on /directory REST endpoint ([#11551](https://github.com/RocketChat/Rocket.Chat/pull/11551)) - Return room ID for groups where user joined ([#11703](https://github.com/RocketChat/Rocket.Chat/pull/11703) by [@timkinnane](https://github.com/timkinnane)) @@ -16960,13 +18163,13 @@ - SAML login not working when user has multiple emails ([#11642](https://github.com/RocketChat/Rocket.Chat/pull/11642) by [@Hudell](https://github.com/Hudell)) -- Searching by `undefined` via REST when using `query` param ([#11657](https://github.com/RocketChat/Rocket.Chat/pull/11657) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Searching by `undefined` via REST when using `query` param ([#11657](https://github.com/RocketChat/Rocket.Chat/pull/11657)) - Some assets were pointing to nonexistent path ([#11796](https://github.com/RocketChat/Rocket.Chat/pull/11796)) - Translations were not unique per app allowing conflicts among apps ([#11878](https://github.com/RocketChat/Rocket.Chat/pull/11878)) -- User info APIs not returning customFields correctly ([#11625](https://github.com/RocketChat/Rocket.Chat/pull/11625) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- User info APIs not returning customFields correctly ([#11625](https://github.com/RocketChat/Rocket.Chat/pull/11625)) - wrong create date of channels and users on directory view ([#11682](https://github.com/RocketChat/Rocket.Chat/pull/11682) by [@gsperezb](https://github.com/gsperezb)) @@ -17004,7 +18207,6 @@ - [@Atisom](https://github.com/Atisom) - [@Hudell](https://github.com/Hudell) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@TheReal1604](https://github.com/TheReal1604) - [@ausminternet](https://github.com/ausminternet) - [@c0dzilla](https://github.com/c0dzilla) @@ -17024,6 +18226,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@engelgabriel](https://github.com/engelgabriel) - [@geekgonecrazy](https://github.com/geekgonecrazy) - [@ggazzo](https://github.com/ggazzo) @@ -17092,24 +18295,24 @@ - SAML login not working when user has multiple emails ([#11642](https://github.com/RocketChat/Rocket.Chat/pull/11642) by [@Hudell](https://github.com/Hudell)) -- User info APIs not returning customFields correctly ([#11625](https://github.com/RocketChat/Rocket.Chat/pull/11625) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- User info APIs not returning customFields correctly ([#11625](https://github.com/RocketChat/Rocket.Chat/pull/11625))
🔍 Minor changes -- Release 0.68.3 ([#11650](https://github.com/RocketChat/Rocket.Chat/pull/11650) by [@Hudell](https://github.com/Hudell) & [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@rndmh3ro](https://github.com/rndmh3ro)) +- Release 0.68.3 ([#11650](https://github.com/RocketChat/Rocket.Chat/pull/11650) by [@Hudell](https://github.com/Hudell) & [@rndmh3ro](https://github.com/rndmh3ro))
### 👩‍💻👨‍💻 Contributors 😍 - [@Hudell](https://github.com/Hudell) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@rndmh3ro](https://github.com/rndmh3ro) ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@sampaiodiego](https://github.com/sampaiodiego) # 0.68.2 @@ -17179,18 +18382,18 @@ ### ⚠️ BREAKING CHANGES -- Remove deprecated /user.roles endpoint ([#11493](https://github.com/RocketChat/Rocket.Chat/pull/11493) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Remove deprecated /user.roles endpoint ([#11493](https://github.com/RocketChat/Rocket.Chat/pull/11493)) -- Update GraphQL dependencies ([#11430](https://github.com/RocketChat/Rocket.Chat/pull/11430) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Update GraphQL dependencies ([#11430](https://github.com/RocketChat/Rocket.Chat/pull/11430)) ### 🎉 New features - Accept resumeToken as query param to log in ([#11443](https://github.com/RocketChat/Rocket.Chat/pull/11443)) -- Add /roles.list REST endpoint to retrieve all server roles ([#11500](https://github.com/RocketChat/Rocket.Chat/pull/11500) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add /roles.list REST endpoint to retrieve all server roles ([#11500](https://github.com/RocketChat/Rocket.Chat/pull/11500)) -- Add /users.deleteOwnAccount REST endpoint to an user delete his own account ([#11488](https://github.com/RocketChat/Rocket.Chat/pull/11488) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add /users.deleteOwnAccount REST endpoint to an user delete his own account ([#11488](https://github.com/RocketChat/Rocket.Chat/pull/11488)) - Livechat File Upload ([#10514](https://github.com/RocketChat/Rocket.Chat/pull/10514)) @@ -17224,7 +18427,7 @@ ### 🐛 Bug fixes -- Add customFields property to /me REST endpoint response ([#11496](https://github.com/RocketChat/Rocket.Chat/pull/11496) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add customFields property to /me REST endpoint response ([#11496](https://github.com/RocketChat/Rocket.Chat/pull/11496)) - broadcast channel reply ([#11462](https://github.com/RocketChat/Rocket.Chat/pull/11462)) @@ -17266,7 +18469,7 @@ - Unlimited upload file size not working ([#11471](https://github.com/RocketChat/Rocket.Chat/pull/11471) by [@Hudell](https://github.com/Hudell)) -- Unreads counter for new rooms on /channels.counters REST endpoint ([#11531](https://github.com/RocketChat/Rocket.Chat/pull/11531) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Unreads counter for new rooms on /channels.counters REST endpoint ([#11531](https://github.com/RocketChat/Rocket.Chat/pull/11531)) - Wrap custom fields in user profile to new line ([#10119](https://github.com/RocketChat/Rocket.Chat/pull/10119) by [@PhpXp](https://github.com/PhpXp) & [@karlprieb](https://github.com/karlprieb)) @@ -17301,7 +18504,6 @@ - [@HappyTobi](https://github.com/HappyTobi) - [@Hudell](https://github.com/Hudell) - [@Joe-mcgee](https://github.com/Joe-mcgee) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@PhpXp](https://github.com/PhpXp) - [@arminfelder](https://github.com/arminfelder) - [@arungalva](https://github.com/arungalva) @@ -17314,6 +18516,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@MartinSchoeler](https://github.com/MartinSchoeler) - [@engelgabriel](https://github.com/engelgabriel) - [@geekgonecrazy](https://github.com/geekgonecrazy) @@ -17514,12 +18717,12 @@ ### ⚠️ BREAKING CHANGES -- Always remove the field `services` from user data responses in REST API ([#10799](https://github.com/RocketChat/Rocket.Chat/pull/10799) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Always remove the field `services` from user data responses in REST API ([#10799](https://github.com/RocketChat/Rocket.Chat/pull/10799)) ### 🎉 New features -- Add input to set time for avatar cache control ([#10958](https://github.com/RocketChat/Rocket.Chat/pull/10958) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add input to set time for avatar cache control ([#10958](https://github.com/RocketChat/Rocket.Chat/pull/10958)) - Add prometheus port config ([#11115](https://github.com/RocketChat/Rocket.Chat/pull/11115) by [@brylie](https://github.com/brylie) & [@stuartpb](https://github.com/stuartpb) & [@thaiphv](https://github.com/thaiphv)) @@ -17581,9 +18784,9 @@ - "blank" screen on iOS < 11 ([#11199](https://github.com/RocketChat/Rocket.Chat/pull/11199)) -- /groups.invite not allow a user to invite even with permission ([#11010](https://github.com/RocketChat/Rocket.Chat/pull/11010) by [@Hudell](https://github.com/Hudell) & [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- /groups.invite not allow a user to invite even with permission ([#11010](https://github.com/RocketChat/Rocket.Chat/pull/11010) by [@Hudell](https://github.com/Hudell)) -- Add parameter to REST chat.react endpoint, to make it work like a setter ([#10447](https://github.com/RocketChat/Rocket.Chat/pull/10447) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add parameter to REST chat.react endpoint, to make it work like a setter ([#10447](https://github.com/RocketChat/Rocket.Chat/pull/10447)) - Allow inviting livechat managers to the same LiveChat room ([#10956](https://github.com/RocketChat/Rocket.Chat/pull/10956)) @@ -17661,9 +18864,9 @@ - Rendering of emails and mentions in messages ([#11165](https://github.com/RocketChat/Rocket.Chat/pull/11165)) -- REST API: Add more test cases for `/login` ([#10999](https://github.com/RocketChat/Rocket.Chat/pull/10999) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- REST API: Add more test cases for `/login` ([#10999](https://github.com/RocketChat/Rocket.Chat/pull/10999)) -- REST endpoint `users.updateOwnBasicInfo` was not returning errors for invalid names and trying to save custom fields when empty ([#11204](https://github.com/RocketChat/Rocket.Chat/pull/11204) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- REST endpoint `users.updateOwnBasicInfo` was not returning errors for invalid names and trying to save custom fields when empty ([#11204](https://github.com/RocketChat/Rocket.Chat/pull/11204)) - Room creation error due absence of subscriptions ([#11178](https://github.com/RocketChat/Rocket.Chat/pull/11178)) @@ -17677,7 +18880,7 @@ - The process was freezing in some cases when HTTP calls exceeds timeout on integrations ([#11253](https://github.com/RocketChat/Rocket.Chat/pull/11253)) -- title and value attachments are optionals on sendMessage method ([#11021](https://github.com/RocketChat/Rocket.Chat/pull/11021) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- title and value attachments are optionals on sendMessage method ([#11021](https://github.com/RocketChat/Rocket.Chat/pull/11021)) - Update capnproto dependence for Sandstorm Build ([#11263](https://github.com/RocketChat/Rocket.Chat/pull/11263) by [@peterlee0127](https://github.com/peterlee0127)) @@ -17703,7 +18906,7 @@ - Add Dockerfile with MongoDB ([#10971](https://github.com/RocketChat/Rocket.Chat/pull/10971)) -- Add verification to make sure the user exists in REST insert object helper ([#11008](https://github.com/RocketChat/Rocket.Chat/pull/11008) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add verification to make sure the user exists in REST insert object helper ([#11008](https://github.com/RocketChat/Rocket.Chat/pull/11008)) - Build Docker image on CI ([#11076](https://github.com/RocketChat/Rocket.Chat/pull/11076)) @@ -17799,7 +19002,6 @@ - [@Hudell](https://github.com/Hudell) - [@JoseRenan](https://github.com/JoseRenan) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@brylie](https://github.com/brylie) - [@c0dzilla](https://github.com/c0dzilla) - [@cardoso](https://github.com/cardoso) @@ -17836,6 +19038,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@alansikora](https://github.com/alansikora) - [@engelgabriel](https://github.com/engelgabriel) - [@geekgonecrazy](https://github.com/geekgonecrazy) @@ -17916,13 +19119,13 @@ - Add permission `view-broadcast-member-list` ([#10753](https://github.com/RocketChat/Rocket.Chat/pull/10753) by [@cardoso](https://github.com/cardoso)) -- Add REST API endpoint `users.getUsernameSuggestion` to get username suggestion ([#10702](https://github.com/RocketChat/Rocket.Chat/pull/10702) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add REST API endpoint `users.getUsernameSuggestion` to get username suggestion ([#10702](https://github.com/RocketChat/Rocket.Chat/pull/10702)) -- Add REST API endpoints `channels.counters`, `groups.counters and `im.counters` ([#9679](https://github.com/RocketChat/Rocket.Chat/pull/9679) by [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@xbolshe](https://github.com/xbolshe)) +- Add REST API endpoints `channels.counters`, `groups.counters and `im.counters` ([#9679](https://github.com/RocketChat/Rocket.Chat/pull/9679) by [@xbolshe](https://github.com/xbolshe)) - Add REST API endpoints `channels.setCustomFields` and `groups.setCustomFields` ([#9733](https://github.com/RocketChat/Rocket.Chat/pull/9733) by [@xbolshe](https://github.com/xbolshe)) -- Add REST endpoint `subscriptions.unread` to mark messages as unread ([#10778](https://github.com/RocketChat/Rocket.Chat/pull/10778) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add REST endpoint `subscriptions.unread` to mark messages as unread ([#10778](https://github.com/RocketChat/Rocket.Chat/pull/10778)) - Add REST endpoints `channels.roles` & `groups.roles` ([#10607](https://github.com/RocketChat/Rocket.Chat/pull/10607) by [@cardoso](https://github.com/cardoso) & [@rafaelks](https://github.com/rafaelks)) @@ -17932,15 +19135,15 @@ - Lazy load image attachments ([#10608](https://github.com/RocketChat/Rocket.Chat/pull/10608) by [@karlprieb](https://github.com/karlprieb)) -- Now is possible to access files using header authorization (`x-user-id` and `x-auth-token`) ([#10741](https://github.com/RocketChat/Rocket.Chat/pull/10741) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Now is possible to access files using header authorization (`x-user-id` and `x-auth-token`) ([#10741](https://github.com/RocketChat/Rocket.Chat/pull/10741)) - Options to enable/disable each Livechat registration form field ([#10584](https://github.com/RocketChat/Rocket.Chat/pull/10584)) -- REST API endpoint `/me` now returns all the settings, including the default values ([#10662](https://github.com/RocketChat/Rocket.Chat/pull/10662) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- REST API endpoint `/me` now returns all the settings, including the default values ([#10662](https://github.com/RocketChat/Rocket.Chat/pull/10662)) -- REST API endpoint `settings` now allow set colors and trigger actions ([#10488](https://github.com/RocketChat/Rocket.Chat/pull/10488) by [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@ThomasRoehl](https://github.com/ThomasRoehl)) +- REST API endpoint `settings` now allow set colors and trigger actions ([#10488](https://github.com/RocketChat/Rocket.Chat/pull/10488) by [@ThomasRoehl](https://github.com/ThomasRoehl)) -- Return the result of the `/me` endpoint within the result of the `/login` endpoint ([#10677](https://github.com/RocketChat/Rocket.Chat/pull/10677) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Return the result of the `/me` endpoint within the result of the `/login` endpoint ([#10677](https://github.com/RocketChat/Rocket.Chat/pull/10677)) - Setup Wizard ([#10523](https://github.com/RocketChat/Rocket.Chat/pull/10523) by [@karlprieb](https://github.com/karlprieb)) @@ -17953,7 +19156,7 @@ - Cancel button wasn't working while uploading file ([#10715](https://github.com/RocketChat/Rocket.Chat/pull/10715) by [@Mr-Gryphon](https://github.com/Mr-Gryphon) & [@karlprieb](https://github.com/karlprieb)) -- Channel owner was being set as muted when creating a read-only channel ([#10665](https://github.com/RocketChat/Rocket.Chat/pull/10665) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Channel owner was being set as muted when creating a read-only channel ([#10665](https://github.com/RocketChat/Rocket.Chat/pull/10665)) - Enabling `Collapse Embedded Media by Default` was hiding replies and quotes ([#10427](https://github.com/RocketChat/Rocket.Chat/pull/10427) by [@c0dzilla](https://github.com/c0dzilla)) @@ -17975,7 +19178,7 @@ - Missing option to disable/enable System Messages ([#10704](https://github.com/RocketChat/Rocket.Chat/pull/10704)) -- Missing pagination fields in the response of REST /directory endpoint ([#10840](https://github.com/RocketChat/Rocket.Chat/pull/10840) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Missing pagination fields in the response of REST /directory endpoint ([#10840](https://github.com/RocketChat/Rocket.Chat/pull/10840)) - Not escaping special chars on mentions ([#10793](https://github.com/RocketChat/Rocket.Chat/pull/10793) by [@erhan-](https://github.com/erhan-)) @@ -17987,7 +19190,7 @@ - SAML wasn't working correctly when running multiple instances ([#10681](https://github.com/RocketChat/Rocket.Chat/pull/10681) by [@Hudell](https://github.com/Hudell)) -- Send a message when muted returns inconsistent result in chat.sendMessage ([#10720](https://github.com/RocketChat/Rocket.Chat/pull/10720) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Send a message when muted returns inconsistent result in chat.sendMessage ([#10720](https://github.com/RocketChat/Rocket.Chat/pull/10720)) - Slack-Bridge bug when migrating to 0.64.1 ([#10875](https://github.com/RocketChat/Rocket.Chat/pull/10875)) @@ -18021,7 +19224,7 @@ - Fix: Manage apps layout was a bit confuse ([#10882](https://github.com/RocketChat/Rocket.Chat/pull/10882) by [@gdelavald](https://github.com/gdelavald)) -- Fix: Regression in REST API endpoint `/me` ([#10833](https://github.com/RocketChat/Rocket.Chat/pull/10833) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Fix: Regression in REST API endpoint `/me` ([#10833](https://github.com/RocketChat/Rocket.Chat/pull/10833)) - Fix: Regression Lazyload fix shuffle avatars ([#10887](https://github.com/RocketChat/Rocket.Chat/pull/10887)) @@ -18057,7 +19260,7 @@ - Regression: Make settings `Site_Name` and `Language` public again ([#10848](https://github.com/RocketChat/Rocket.Chat/pull/10848)) -- Release 0.65.0 ([#10893](https://github.com/RocketChat/Rocket.Chat/pull/10893) by [@Hudell](https://github.com/Hudell) & [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@Sameesunkaria](https://github.com/Sameesunkaria) & [@cardoso](https://github.com/cardoso) & [@erhan-](https://github.com/erhan-) & [@gdelavald](https://github.com/gdelavald) & [@karlprieb](https://github.com/karlprieb) & [@peccu](https://github.com/peccu) & [@winterstefan](https://github.com/winterstefan)) +- Release 0.65.0 ([#10893](https://github.com/RocketChat/Rocket.Chat/pull/10893) by [@Hudell](https://github.com/Hudell) & [@Sameesunkaria](https://github.com/Sameesunkaria) & [@cardoso](https://github.com/cardoso) & [@erhan-](https://github.com/erhan-) & [@gdelavald](https://github.com/gdelavald) & [@karlprieb](https://github.com/karlprieb) & [@peccu](https://github.com/peccu) & [@winterstefan](https://github.com/winterstefan)) - Wizard improvements ([#10776](https://github.com/RocketChat/Rocket.Chat/pull/10776)) @@ -18066,7 +19269,6 @@ ### 👩‍💻👨‍💻 Contributors 😍 - [@Hudell](https://github.com/Hudell) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@Mr-Gryphon](https://github.com/Mr-Gryphon) - [@Sameesunkaria](https://github.com/Sameesunkaria) - [@ThomasRoehl](https://github.com/ThomasRoehl) @@ -18086,6 +19288,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@engelgabriel](https://github.com/engelgabriel) - [@geekgonecrazy](https://github.com/geekgonecrazy) - [@ggazzo](https://github.com/ggazzo) @@ -18105,14 +19308,13 @@ 🔍 Minor changes -- Release 0.64.2 ([#10812](https://github.com/RocketChat/Rocket.Chat/pull/10812) by [@Hudell](https://github.com/Hudell) & [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@Sameesunkaria](https://github.com/Sameesunkaria) & [@cardoso](https://github.com/cardoso) & [@erhan-](https://github.com/erhan-) & [@gdelavald](https://github.com/gdelavald) & [@karlprieb](https://github.com/karlprieb) & [@peccu](https://github.com/peccu) & [@winterstefan](https://github.com/winterstefan)) +- Release 0.64.2 ([#10812](https://github.com/RocketChat/Rocket.Chat/pull/10812) by [@Hudell](https://github.com/Hudell) & [@Sameesunkaria](https://github.com/Sameesunkaria) & [@cardoso](https://github.com/cardoso) & [@erhan-](https://github.com/erhan-) & [@gdelavald](https://github.com/gdelavald) & [@karlprieb](https://github.com/karlprieb) & [@peccu](https://github.com/peccu) & [@winterstefan](https://github.com/winterstefan))
### 👩‍💻👨‍💻 Contributors 😍 - [@Hudell](https://github.com/Hudell) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@Sameesunkaria](https://github.com/Sameesunkaria) - [@cardoso](https://github.com/cardoso) - [@erhan-](https://github.com/erhan-) @@ -18123,6 +19325,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@engelgabriel](https://github.com/engelgabriel) - [@rodrigok](https://github.com/rodrigok) - [@sampaiodiego](https://github.com/sampaiodiego) @@ -18183,7 +19386,7 @@ - The property "settings" is no longer available to regular users via rest api ([#10411](https://github.com/RocketChat/Rocket.Chat/pull/10411)) -- Validate incoming message schema ([#9922](https://github.com/RocketChat/Rocket.Chat/pull/9922) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Validate incoming message schema ([#9922](https://github.com/RocketChat/Rocket.Chat/pull/9922)) ### 🎉 New features @@ -18210,13 +19413,13 @@ - Prevent the browser to autocomplete some setting fields ([#10439](https://github.com/RocketChat/Rocket.Chat/pull/10439) by [@gdelavald](https://github.com/gdelavald)) -- REST API endpoint `/directory` ([#10442](https://github.com/RocketChat/Rocket.Chat/pull/10442) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- REST API endpoint `/directory` ([#10442](https://github.com/RocketChat/Rocket.Chat/pull/10442)) -- REST API endpoint `rooms.favorite` to favorite and unfavorite rooms ([#10342](https://github.com/RocketChat/Rocket.Chat/pull/10342) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- REST API endpoint `rooms.favorite` to favorite and unfavorite rooms ([#10342](https://github.com/RocketChat/Rocket.Chat/pull/10342)) -- REST endpoint to recover forgotten password ([#10371](https://github.com/RocketChat/Rocket.Chat/pull/10371) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- REST endpoint to recover forgotten password ([#10371](https://github.com/RocketChat/Rocket.Chat/pull/10371)) -- REST endpoint to report messages ([#10354](https://github.com/RocketChat/Rocket.Chat/pull/10354) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- REST endpoint to report messages ([#10354](https://github.com/RocketChat/Rocket.Chat/pull/10354)) - Search Provider Framework ([#10110](https://github.com/RocketChat/Rocket.Chat/pull/10110) by [@tkurz](https://github.com/tkurz)) @@ -18231,7 +19434,7 @@ - "Idle Time Limit" using milliseconds instead of seconds ([#9824](https://github.com/RocketChat/Rocket.Chat/pull/9824) by [@kaiiiiiiiii](https://github.com/kaiiiiiiiii)) -- Add user object to responses in /*.files Rest endpoints ([#10480](https://github.com/RocketChat/Rocket.Chat/pull/10480) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add user object to responses in /*.files Rest endpoints ([#10480](https://github.com/RocketChat/Rocket.Chat/pull/10480)) - Autocomplete list when inviting a user was partial hidden ([#10409](https://github.com/RocketChat/Rocket.Chat/pull/10409) by [@karlprieb](https://github.com/karlprieb)) @@ -18285,13 +19488,13 @@ - Remove a user from the user's list when creating a new channel removes the wrong user ([#10423](https://github.com/RocketChat/Rocket.Chat/pull/10423) by [@gdelavald](https://github.com/gdelavald) & [@karlprieb](https://github.com/karlprieb)) -- Rename method to clean history of messages ([#10498](https://github.com/RocketChat/Rocket.Chat/pull/10498) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Rename method to clean history of messages ([#10498](https://github.com/RocketChat/Rocket.Chat/pull/10498)) - Renaming agent's username within Livechat's department ([#10344](https://github.com/RocketChat/Rocket.Chat/pull/10344)) -- REST API OAuth services endpoint were missing fields and flag to indicate custom services ([#10299](https://github.com/RocketChat/Rocket.Chat/pull/10299) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- REST API OAuth services endpoint were missing fields and flag to indicate custom services ([#10299](https://github.com/RocketChat/Rocket.Chat/pull/10299)) -- REST spotlight API wasn't allowing searches with # and @ ([#10410](https://github.com/RocketChat/Rocket.Chat/pull/10410) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- REST spotlight API wasn't allowing searches with # and @ ([#10410](https://github.com/RocketChat/Rocket.Chat/pull/10410)) - Room's name was cutting instead of having ellipses on sidebar ([#10430](https://github.com/RocketChat/Rocket.Chat/pull/10430)) @@ -18337,7 +19540,7 @@ - Fix and improve vietnamese translation ([#10397](https://github.com/RocketChat/Rocket.Chat/pull/10397) by [@TDiNguyen](https://github.com/TDiNguyen) & [@tttt-conan](https://github.com/tttt-conan)) -- Fix: Remove "secret" from REST endpoint /settings.oauth response ([#10513](https://github.com/RocketChat/Rocket.Chat/pull/10513) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Fix: Remove "secret" from REST endpoint /settings.oauth response ([#10513](https://github.com/RocketChat/Rocket.Chat/pull/10513)) - Included missing lib for migrations ([#10532](https://github.com/RocketChat/Rocket.Chat/pull/10532) by [@Hudell](https://github.com/Hudell)) @@ -18357,7 +19560,7 @@ - Regression: Fix announcement bar being displayed without content ([#10554](https://github.com/RocketChat/Rocket.Chat/pull/10554) by [@gdelavald](https://github.com/gdelavald)) -- Regression: Inconsistent response of settings.oauth endpoint ([#10553](https://github.com/RocketChat/Rocket.Chat/pull/10553) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Regression: Inconsistent response of settings.oauth endpoint ([#10553](https://github.com/RocketChat/Rocket.Chat/pull/10553)) - Regression: Remove added mentions on quote/reply ([#10571](https://github.com/RocketChat/Rocket.Chat/pull/10571) by [@gdelavald](https://github.com/gdelavald)) @@ -18386,7 +19589,6 @@ ### 👩‍💻👨‍💻 Contributors 😍 - [@Hudell](https://github.com/Hudell) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@Prakharsvnit](https://github.com/Prakharsvnit) - [@TDiNguyen](https://github.com/TDiNguyen) - [@TwizzyDizzy](https://github.com/TwizzyDizzy) @@ -18411,6 +19613,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@engelgabriel](https://github.com/engelgabriel) - [@geekgonecrazy](https://github.com/geekgonecrazy) - [@ggazzo](https://github.com/ggazzo) @@ -18509,21 +19712,21 @@ - Add leave public channel & leave private channel permissions ([#9584](https://github.com/RocketChat/Rocket.Chat/pull/9584) by [@kb0304](https://github.com/kb0304)) -- Add option to login via REST using Facebook and Twitter tokens ([#9816](https://github.com/RocketChat/Rocket.Chat/pull/9816) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add option to login via REST using Facebook and Twitter tokens ([#9816](https://github.com/RocketChat/Rocket.Chat/pull/9816)) -- Add REST endpoint to get the list of custom emojis ([#9629](https://github.com/RocketChat/Rocket.Chat/pull/9629) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Add REST endpoint to get the list of custom emojis ([#9629](https://github.com/RocketChat/Rocket.Chat/pull/9629)) -- Added endpoint to get the list of available oauth services ([#10144](https://github.com/RocketChat/Rocket.Chat/pull/10144) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Added endpoint to get the list of available oauth services ([#10144](https://github.com/RocketChat/Rocket.Chat/pull/10144)) -- Added endpoint to retrieve mentions of a channel ([#10105](https://github.com/RocketChat/Rocket.Chat/pull/10105) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Added endpoint to retrieve mentions of a channel ([#10105](https://github.com/RocketChat/Rocket.Chat/pull/10105)) -- Added GET/POST channels.notifications ([#10128](https://github.com/RocketChat/Rocket.Chat/pull/10128) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Added GET/POST channels.notifications ([#10128](https://github.com/RocketChat/Rocket.Chat/pull/10128)) - Announcement bar color wasn't using color from theming variables ([#9367](https://github.com/RocketChat/Rocket.Chat/pull/9367) by [@cyclops24](https://github.com/cyclops24) & [@karlprieb](https://github.com/karlprieb)) - Audio recording as mp3 and better ui for recording ([#9726](https://github.com/RocketChat/Rocket.Chat/pull/9726) by [@kb0304](https://github.com/kb0304)) -- Endpoint to retrieve message read receipts ([#9907](https://github.com/RocketChat/Rocket.Chat/pull/9907) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Endpoint to retrieve message read receipts ([#9907](https://github.com/RocketChat/Rocket.Chat/pull/9907)) - GDPR Right to be forgotten/erased ([#9947](https://github.com/RocketChat/Rocket.Chat/pull/9947) by [@Hudell](https://github.com/Hudell)) @@ -18548,9 +19751,9 @@ - "View All Members" button inside channel's "User Info" is over sized ([#10012](https://github.com/RocketChat/Rocket.Chat/pull/10012) by [@karlprieb](https://github.com/karlprieb)) -- /me REST endpoint was missing user roles and preferences ([#10240](https://github.com/RocketChat/Rocket.Chat/pull/10240) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- /me REST endpoint was missing user roles and preferences ([#10240](https://github.com/RocketChat/Rocket.Chat/pull/10240)) -- Able to react with invalid emoji ([#8667](https://github.com/RocketChat/Rocket.Chat/pull/8667) by [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@mutdmour](https://github.com/mutdmour)) +- Able to react with invalid emoji ([#8667](https://github.com/RocketChat/Rocket.Chat/pull/8667) by [@mutdmour](https://github.com/mutdmour)) - Apostrophe-containing URL misparsed ([#9739](https://github.com/RocketChat/Rocket.Chat/pull/9739) by [@lunaticmonk](https://github.com/lunaticmonk)) @@ -18598,7 +19801,7 @@ - Reactions not working on mobile ([#10104](https://github.com/RocketChat/Rocket.Chat/pull/10104)) -- REST API: Can't list all public channels when user has permission `view-joined-room` ([#10009](https://github.com/RocketChat/Rocket.Chat/pull/10009) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- REST API: Can't list all public channels when user has permission `view-joined-room` ([#10009](https://github.com/RocketChat/Rocket.Chat/pull/10009)) - Slack Import reports `invalid import file type` due to a call to BSON.native() which is now doesn't exist ([#10071](https://github.com/RocketChat/Rocket.Chat/pull/10071) by [@trongthanh](https://github.com/trongthanh)) @@ -18612,9 +19815,9 @@ - user status on sidenav ([#10222](https://github.com/RocketChat/Rocket.Chat/pull/10222)) -- Verified property of user is always set to false if not supplied ([#9719](https://github.com/RocketChat/Rocket.Chat/pull/9719) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Verified property of user is always set to false if not supplied ([#9719](https://github.com/RocketChat/Rocket.Chat/pull/9719)) -- Wrong pagination information on /api/v1/channels.members ([#10224](https://github.com/RocketChat/Rocket.Chat/pull/10224) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Wrong pagination information on /api/v1/channels.members ([#10224](https://github.com/RocketChat/Rocket.Chat/pull/10224)) - Wrong switch button border color ([#10081](https://github.com/RocketChat/Rocket.Chat/pull/10081) by [@kb0304](https://github.com/kb0304)) @@ -18624,7 +19827,7 @@ - [OTHER] Reactivate all tests ([#10036](https://github.com/RocketChat/Rocket.Chat/pull/10036)) -- [OTHER] Reactivate API tests ([#9844](https://github.com/RocketChat/Rocket.Chat/pull/9844) by [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@karlprieb](https://github.com/karlprieb)) +- [OTHER] Reactivate API tests ([#9844](https://github.com/RocketChat/Rocket.Chat/pull/9844) by [@karlprieb](https://github.com/karlprieb)) - Add a few listener supports for the Rocket.Chat Apps ([#10154](https://github.com/RocketChat/Rocket.Chat/pull/10154)) @@ -18648,13 +19851,13 @@ - Fix: Reaction endpoint/api only working with regular emojis ([#10323](https://github.com/RocketChat/Rocket.Chat/pull/10323)) -- Fix: Renaming channels.notifications Get/Post endpoints ([#10257](https://github.com/RocketChat/Rocket.Chat/pull/10257) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Fix: Renaming channels.notifications Get/Post endpoints ([#10257](https://github.com/RocketChat/Rocket.Chat/pull/10257)) - Fix: Scroll on content page ([#10300](https://github.com/RocketChat/Rocket.Chat/pull/10300)) - LingoHub based on develop ([#10243](https://github.com/RocketChat/Rocket.Chat/pull/10243)) -- Release 0.63.0 ([#10324](https://github.com/RocketChat/Rocket.Chat/pull/10324) by [@Hudell](https://github.com/Hudell) & [@Joe-mcgee](https://github.com/Joe-mcgee) & [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@TopHattedCat](https://github.com/TopHattedCat) & [@hmagarotto](https://github.com/hmagarotto) & [@kaiiiiiiiii](https://github.com/kaiiiiiiiii) & [@karlprieb](https://github.com/karlprieb) & [@kb0304](https://github.com/kb0304) & [@lunaticmonk](https://github.com/lunaticmonk) & [@ramrami](https://github.com/ramrami)) +- Release 0.63.0 ([#10324](https://github.com/RocketChat/Rocket.Chat/pull/10324) by [@Hudell](https://github.com/Hudell) & [@Joe-mcgee](https://github.com/Joe-mcgee) & [@TopHattedCat](https://github.com/TopHattedCat) & [@hmagarotto](https://github.com/hmagarotto) & [@kaiiiiiiiii](https://github.com/kaiiiiiiiii) & [@karlprieb](https://github.com/karlprieb) & [@kb0304](https://github.com/kb0304) & [@lunaticmonk](https://github.com/lunaticmonk) & [@ramrami](https://github.com/ramrami)) - Rename migration name on 108 to match file name ([#10237](https://github.com/RocketChat/Rocket.Chat/pull/10237)) @@ -18668,7 +19871,6 @@ - [@Hudell](https://github.com/Hudell) - [@Joe-mcgee](https://github.com/Joe-mcgee) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@SeanPackham](https://github.com/SeanPackham) - [@TopHattedCat](https://github.com/TopHattedCat) - [@bernardoetrevisan](https://github.com/bernardoetrevisan) @@ -18687,6 +19889,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@engelgabriel](https://github.com/engelgabriel) - [@geekgonecrazy](https://github.com/geekgonecrazy) - [@ggazzo](https://github.com/ggazzo) @@ -18709,13 +19912,13 @@ - Message editing is crashing the server when read receipts are enabled ([#10061](https://github.com/RocketChat/Rocket.Chat/pull/10061)) -- REST API: Can't list all public channels when user has permission `view-joined-room` ([#10009](https://github.com/RocketChat/Rocket.Chat/pull/10009) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- REST API: Can't list all public channels when user has permission `view-joined-room` ([#10009](https://github.com/RocketChat/Rocket.Chat/pull/10009)) - Slack Import reports `invalid import file type` due to a call to BSON.native() which is now doesn't exist ([#10071](https://github.com/RocketChat/Rocket.Chat/pull/10071) by [@trongthanh](https://github.com/trongthanh)) - Update preferences of users with settings: null was crashing the server ([#10076](https://github.com/RocketChat/Rocket.Chat/pull/10076)) -- Verified property of user is always set to false if not supplied ([#9719](https://github.com/RocketChat/Rocket.Chat/pull/9719) by [@MarcosSpessatto](https://github.com/MarcosSpessatto)) +- Verified property of user is always set to false if not supplied ([#9719](https://github.com/RocketChat/Rocket.Chat/pull/9719))
🔍 Minor changes @@ -18727,11 +19930,11 @@ ### 👩‍💻👨‍💻 Contributors 😍 -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@trongthanh](https://github.com/trongthanh) ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@rodrigok](https://github.com/rodrigok) - [@sampaiodiego](https://github.com/sampaiodiego) @@ -18790,7 +19993,7 @@ - Add route to get user shield/badge ([#9549](https://github.com/RocketChat/Rocket.Chat/pull/9549) by [@kb0304](https://github.com/kb0304)) -- Add user settings / preferences API endpoint ([#9457](https://github.com/RocketChat/Rocket.Chat/pull/9457) by [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@jgtoriginal](https://github.com/jgtoriginal)) +- Add user settings / preferences API endpoint ([#9457](https://github.com/RocketChat/Rocket.Chat/pull/9457) by [@jgtoriginal](https://github.com/jgtoriginal)) - Alert admins when user requires approval & alert users when the account is approved/activated/deactivated ([#7098](https://github.com/RocketChat/Rocket.Chat/pull/7098) by [@luisfn](https://github.com/luisfn)) @@ -18800,7 +20003,7 @@ - Allow sounds when conversation is focused ([#9312](https://github.com/RocketChat/Rocket.Chat/pull/9312) by [@RationalCoding](https://github.com/RationalCoding)) -- API to fetch permissions & user roles ([#9519](https://github.com/RocketChat/Rocket.Chat/pull/9519) by [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@rafaelks](https://github.com/rafaelks)) +- API to fetch permissions & user roles ([#9519](https://github.com/RocketChat/Rocket.Chat/pull/9519) by [@rafaelks](https://github.com/rafaelks)) - Browse more channels / Directory ([#9642](https://github.com/RocketChat/Rocket.Chat/pull/9642) by [@karlprieb](https://github.com/karlprieb)) @@ -18830,7 +20033,7 @@ - Request mongoDB version in github issue template ([#9807](https://github.com/RocketChat/Rocket.Chat/pull/9807) by [@TwizzyDizzy](https://github.com/TwizzyDizzy)) -- REST API to use Spotlight ([#9509](https://github.com/RocketChat/Rocket.Chat/pull/9509) by [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@rafaelks](https://github.com/rafaelks)) +- REST API to use Spotlight ([#9509](https://github.com/RocketChat/Rocket.Chat/pull/9509) by [@rafaelks](https://github.com/rafaelks)) - Version update check ([#9793](https://github.com/RocketChat/Rocket.Chat/pull/9793)) @@ -18841,7 +20044,7 @@ - API to retrive rooms was returning empty objects ([#9737](https://github.com/RocketChat/Rocket.Chat/pull/9737)) -- Chat Message Reactions REST API End Point ([#9487](https://github.com/RocketChat/Rocket.Chat/pull/9487) by [@MarcosSpessatto](https://github.com/MarcosSpessatto) & [@jgtoriginal](https://github.com/jgtoriginal)) +- Chat Message Reactions REST API End Point ([#9487](https://github.com/RocketChat/Rocket.Chat/pull/9487) by [@jgtoriginal](https://github.com/jgtoriginal)) - Chrome 64 breaks jitsi-meet iframe ([#9560](https://github.com/RocketChat/Rocket.Chat/pull/9560) by [@speedy01](https://github.com/speedy01)) @@ -18964,7 +20167,6 @@ - [@AmShaegar13](https://github.com/AmShaegar13) - [@HammyHavoc](https://github.com/HammyHavoc) - [@JSzaszvari](https://github.com/JSzaszvari) -- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@RationalCoding](https://github.com/RationalCoding) - [@SeanPackham](https://github.com/SeanPackham) - [@TwizzyDizzy](https://github.com/TwizzyDizzy) @@ -18995,6 +20197,7 @@ ### 👩‍💻👨‍💻 Core Team 🤓 +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) - [@MartinSchoeler](https://github.com/MartinSchoeler) - [@engelgabriel](https://github.com/engelgabriel) - [@geekgonecrazy](https://github.com/geekgonecrazy) diff --git a/README.md b/README.md index d05db47fbd48..1c7965f289d0 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Please refer to [Install Rocket.Chat](https://rocket.chat/install) to install yo Join thousands of members worldwide in our [community server](https://open.rocket.chat). Join [#Support](https://open.rocket.chat/channel/support) for help from our community with general Rocket.Chat questions. Join [#Dev](https://open.rocket.chat/channel/dev) for needing help from the community to develop new features. -Talk with Rocket.Chat's leadership at the [Community Open Call](https://www.youtube.com/watch?v=RdbqOdUb3Wk), held monthly. Join us for [the next Community Open Call](https://app.livestorm.co/rocket-chat/community-open-call?type=detailed). +Talk with Rocket.Chat's leadership at the [Community Open Call](https://www.youtube.com/playlist?list=PLee3gqXJQrFVaxryc0OKTKc92yqQX9U-5), held monthly. Join us for [the next Community Open Call](https://app.livestorm.co/rocket-chat/community-open-call?type=detailed). ## Contributions @@ -55,7 +55,7 @@ In addition to the web interface, you can also download Rocket.Chat clients for: [![Rocket.Chat on Apple App Store](https://user-images.githubusercontent.com/551004/29770691-a2082ff4-8bc6-11e7-89a6-964cd405ea8e.png)](https://itunes.apple.com/us/app/rocket-chat/id1148741252?mt=8) [![Rocket.Chat on Google Play](https://user-images.githubusercontent.com/551004/29770692-a20975c6-8bc6-11e7-8ab0-1cde275496e0.png)](https://play.google.com/store/apps/details?id=chat.rocket.android) [![](https://user-images.githubusercontent.com/551004/48210349-50649480-e35e-11e8-97d9-74a4331faf3a.png)](https://f-droid.org/en/packages/chat.rocket.android) ## Learn More -* [API](https://developer.rocket.chat) +* [API](https://developer.rocket.chat/reference/api) * [See who's using Rocket.Chat](https://rocket.chat/customer-stories) ## Become a Rocketeer @@ -70,6 +70,6 @@ We're hiring developers, support people, and product managers all the time. Plea * [Youtube](https://www.youtube.com/channel/UCin9nv7mUjoqrRiwrzS5UVQ) * [Email Newsletter](https://rocket.chat/newsletter) -Any other questions, reach out to us at [contact@rocket.chat](contact@rocket.chat). We’d happy to help! +Any other questions, reach out to us at [our website](https://rocket.chat/contact) or you can email us directly at [contact@rocket.chat](mailto:contact@rocket.chat). We’d be happy to help! diff --git a/app/2fa/server/code/EmailCheck.ts b/app/2fa/server/code/EmailCheck.ts index 8ef050637340..8b2ddb3814c6 100644 --- a/app/2fa/server/code/EmailCheck.ts +++ b/app/2fa/server/code/EmailCheck.ts @@ -117,10 +117,14 @@ ${t('If_you_didnt_try_to_login_in_your_account_please_ignore_this_email')} const expireWithDelta = new Date(); expireWithDelta.setMinutes(expireWithDelta.getMinutes() - 5); - const hasValidCode = user.services?.emailCode?.filter(({ expire }) => expire > expireWithDelta); + const emails = this.getUserVerifiedEmails(user); + const emailOrUsername = user.username || emails[0]; + + const hasValidCode = user.services?.emailCode?.filter(({ expire }) => expire > expireWithDelta); if (hasValidCode?.length) { return { + emailOrUsername, codeGenerated: false, codeCount: hasValidCode.length, codeExpires: hasValidCode.map((i) => i.expire), @@ -131,6 +135,7 @@ ${t('If_you_didnt_try_to_login_in_your_account_please_ignore_this_email')} return { codeGenerated: true, + emailOrUsername, }; } } diff --git a/app/2fa/server/code/ICodeCheck.ts b/app/2fa/server/code/ICodeCheck.ts index 3cdd9fb6f4e7..4c91f44f32ca 100644 --- a/app/2fa/server/code/ICodeCheck.ts +++ b/app/2fa/server/code/ICodeCheck.ts @@ -4,6 +4,7 @@ export interface IProcessInvalidCodeResult { codeGenerated: boolean; codeCount?: number; codeExpires?: Date[]; + emailOrUsername?: string; } export interface ICodeCheck { diff --git a/app/2fa/server/code/PasswordCheckFallback.ts b/app/2fa/server/code/PasswordCheckFallback.ts index ed6a3898d9a8..71757c83e8c1 100644 --- a/app/2fa/server/code/PasswordCheckFallback.ts +++ b/app/2fa/server/code/PasswordCheckFallback.ts @@ -24,7 +24,7 @@ export class PasswordCheckFallback implements ICodeCheck { return false; } - const passCheck = Accounts._checkPassword(user, { + const passCheck = Accounts._checkPassword(user as Meteor.User, { digest: code.toLowerCase(), algorithm: 'sha-256', }); diff --git a/app/2fa/server/code/index.ts b/app/2fa/server/code/index.ts index 056db89c3715..34c1e767bbac 100644 --- a/app/2fa/server/code/index.ts +++ b/app/2fa/server/code/index.ts @@ -187,8 +187,9 @@ export function checkCodeForUser({ user, code, method, options = {}, connection return true; } + const data = selectedMethod.processInvalidCode(user); + if (!code) { - const data = selectedMethod.processInvalidCode(user); const availableMethods = getAvailableMethodNames(user); throw new Meteor.Error('totp-required', 'TOTP Required', { @@ -199,9 +200,11 @@ export function checkCodeForUser({ user, code, method, options = {}, connection } const valid = selectedMethod.verify(user, code, options.requireSecondFactor); - if (!valid) { - throw new Meteor.Error('totp-invalid', 'TOTP Invalid', { method: selectedMethod.name }); + throw new Meteor.Error('totp-invalid', 'TOTP Invalid', { + method: selectedMethod.name, + ...data, + }); } if (options.disableRememberMe !== true && connection) { diff --git a/app/2fa/server/functions/resetTOTP.ts b/app/2fa/server/functions/resetTOTP.ts index d0b17778976d..35b98cdc5e2a 100644 --- a/app/2fa/server/functions/resetTOTP.ts +++ b/app/2fa/server/functions/resetTOTP.ts @@ -15,7 +15,7 @@ const sendResetNotification = async function (uid: string): Promise { } const language = user.language || settings.get('Language') || 'en'; - const addresses = user.emails?.filter(({ verified }: { verified: boolean }) => verified).map((e) => e.address); + const addresses = user.emails?.filter(({ verified }) => Boolean(verified)).map((e) => e.address); if (!addresses?.length) { return; } diff --git a/app/2fa/server/twoFactorRequired.ts b/app/2fa/server/twoFactorRequired.ts index 0fb1e8cd9d1f..1cda35f5bcca 100644 --- a/app/2fa/server/twoFactorRequired.ts +++ b/app/2fa/server/twoFactorRequired.ts @@ -1,10 +1,12 @@ import { Meteor } from 'meteor/meteor'; import { checkCodeForUser, ITwoFactorOptions } from './code/index'; -import { IMethodThisType } from '../../../definition/IMethodThisType'; -export function twoFactorRequired(fn: Function, options: ITwoFactorOptions): Function { - return function (this: IMethodThisType, ...args: any[]): any { +export function twoFactorRequired any>( + fn: TFunction, + options?: ITwoFactorOptions, +): (this: Meteor.MethodThisType, ...args: Parameters) => ReturnType { + return function (this: Meteor.MethodThisType, ...args: Parameters): ReturnType { if (!this.userId) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'twoFactorRequired' }); } diff --git a/app/action-links/client/lib/actionLinks.js b/app/action-links/client/lib/actionLinks.js index 42aef4d55dcf..6586a42205a6 100644 --- a/app/action-links/client/lib/actionLinks.js +++ b/app/action-links/client/lib/actionLinks.js @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { handleError } from '../../../../client/lib/utils/handleError'; -import { Messages, Subscriptions } from '../../../models/client'; +import { Messages } from '../../../models/client'; // Action Links namespace creation. export const actionLinks = { @@ -24,16 +24,6 @@ export const actionLinks = { }); } - const subscription = Subscriptions.findOne({ - 'rid': message.rid, - 'u._id': userId, - }); - if (!subscription) { - throw new Meteor.Error('error-not-allowed', 'Not allowed', { - function: 'actionLinks.getMessage', - }); - } - if (!message.actionLinks || !message.actionLinks[name]) { throw new Meteor.Error('error-invalid-actionlink', 'Invalid action link', { function: 'actionLinks.getMessage', diff --git a/app/action-links/server/lib/actionLinks.js b/app/action-links/server/lib/actionLinks.js index 6dbbd4c5c4c1..f04553d6656c 100644 --- a/app/action-links/server/lib/actionLinks.js +++ b/app/action-links/server/lib/actionLinks.js @@ -1,6 +1,16 @@ import { Meteor } from 'meteor/meteor'; -import { Messages, Subscriptions } from '../../../models/server'; +import { getMessageForUser } from '../../../../server/lib/messages/getMessageForUser'; + +function getMessageById(messageId) { + try { + return Promise.await(getMessageForUser(messageId, Meteor.userId())); + } catch (e) { + throw new Meteor.Error(e.message, 'Invalid message', { + function: 'actionLinks.getMessage', + }); + } +} // Action Links namespace creation. export const actionLinks = { @@ -9,30 +19,14 @@ export const actionLinks = { actionLinks.actions[name] = funct; }, getMessage(name, messageId) { - const userId = Meteor.userId(); - if (!userId) { - throw new Meteor.Error('error-invalid-user', 'Invalid user', { - function: 'actionLinks.getMessage', - }); - } + const message = getMessageById(messageId); - const message = Messages.findOne({ _id: messageId }); if (!message) { throw new Meteor.Error('error-invalid-message', 'Invalid message', { function: 'actionLinks.getMessage', }); } - const subscription = Subscriptions.findOne({ - 'rid': message.rid, - 'u._id': userId, - }); - if (!subscription) { - throw new Meteor.Error('error-not-allowed', 'Not allowed', { - function: 'actionLinks.getMessage', - }); - } - if (!message.actionLinks || !message.actionLinks[name]) { throw new Meteor.Error('error-invalid-actionlink', 'Invalid action link', { function: 'actionLinks.getMessage', diff --git a/app/analytics/server/settings.ts b/app/analytics/server/settings.ts index f2ee6e94d04d..ef1f6b5f073c 100644 --- a/app/analytics/server/settings.ts +++ b/app/analytics/server/settings.ts @@ -83,5 +83,9 @@ settingsRegistry.addGroup('Analytics', function addSettings() { i18nLabel: 'Users', i18nDescription: 'Analytics_features_users_Description', }); + this.add('Engagement_Dashboard_Load_Count', 0, { + type: 'int', + hidden: true, + }); }); }); diff --git a/app/api/server/api.d.ts b/app/api/server/api.d.ts index 8f478094929d..79caba6f763b 100644 --- a/app/api/server/api.d.ts +++ b/app/api/server/api.d.ts @@ -30,11 +30,11 @@ type UnauthorizedResult = { }; }; -type NotFoundResult = { - statusCode: 403; +type NotFoundResult = { + statusCode: 404; body: { success: false; - error: T | 'Resource not found'; + error: string; }; }; @@ -96,7 +96,8 @@ type ActionThis = | SuccessResult> | FailureResult - | UnauthorizedResult; + | UnauthorizedResult + | NotFoundResult; type Action = | ((this: ActionThis) => Promise>) @@ -113,6 +114,15 @@ type Operations }; declare class APIClass { + fieldSeparator(fieldSeparator: unknown): void; + + limitedUserFieldsToExclude(fields: { [x: string]: unknown }, limitedUserFieldsToExclude: unknown): { [x: string]: unknown }; + + limitedUserFieldsToExcludeIfIsPrivilegedUser( + fields: { [x: string]: unknown }, + limitedUserFieldsToExcludeIfIsPrivilegedUser: unknown, + ): { [x: string]: unknown }; + processTwoFactor({ userId, request, @@ -164,9 +174,9 @@ declare class APIClass { failure(): FailureResult; - unauthorized(msg?: T): UnauthorizedResult; + notFound(msg?: string): NotFoundResult; - notFound(msg?: T): NotFoundResult; + unauthorized(msg?: T): UnauthorizedResult; defaultFieldsToExclude: { joinCode: 0; @@ -179,4 +189,5 @@ declare class APIClass { export declare const API: { v1: APIClass<'/v1'>; default: APIClass; + helperMethods: Map unknown>; }; diff --git a/app/api/server/api.js b/app/api/server/api.js index ec870075ea55..9d6ddfb11718 100644 --- a/app/api/server/api.js +++ b/app/api/server/api.js @@ -453,10 +453,8 @@ export class APIClass extends Restivus { return result; }; - if (this.hasHelperMethods()) { - for (const [name, helperMethod] of this.getHelperMethods()) { - endpoints[method][name] = helperMethod; - } + for (const [name, helperMethod] of this.getHelperMethods()) { + endpoints[method][name] = helperMethod; } // Allow the endpoints to make usage of the logger which respects the user's settings diff --git a/app/api/server/helpers/composeRoomWithLastMessage.js b/app/api/server/helpers/composeRoomWithLastMessage.ts similarity index 75% rename from app/api/server/helpers/composeRoomWithLastMessage.js rename to app/api/server/helpers/composeRoomWithLastMessage.ts index 8822e6d575cc..8b2750405206 100644 --- a/app/api/server/helpers/composeRoomWithLastMessage.js +++ b/app/api/server/helpers/composeRoomWithLastMessage.ts @@ -1,7 +1,8 @@ +import { IRoom } from '../../../../definition/IRoom'; import { normalizeMessagesForUser } from '../../../utils/server/lib/normalizeMessagesForUser'; import { API } from '../api'; -API.helperMethods.set('composeRoomWithLastMessage', function _composeRoomWithLastMessage(room, userId) { +API.helperMethods.set('composeRoomWithLastMessage', function _composeRoomWithLastMessage(room: IRoom, userId: string) { if (room.lastMessage) { const [lastMessage] = normalizeMessagesForUser([room.lastMessage], userId); room.lastMessage = lastMessage; diff --git a/app/api/server/helpers/deprecationWarning.ts b/app/api/server/helpers/deprecationWarning.ts index f1dbb562f1d6..5487d97d54d2 100644 --- a/app/api/server/helpers/deprecationWarning.ts +++ b/app/api/server/helpers/deprecationWarning.ts @@ -22,4 +22,4 @@ export function deprecationWarning({ return response; } -(API as any).helperMethods.set('deprecationWarning', deprecationWarning); +API.helperMethods.set('deprecationWarning', deprecationWarning); diff --git a/app/api/server/helpers/getLoggedInUser.js b/app/api/server/helpers/getLoggedInUser.ts similarity index 79% rename from app/api/server/helpers/getLoggedInUser.js rename to app/api/server/helpers/getLoggedInUser.ts index 71d398c19b82..6da949a29178 100644 --- a/app/api/server/helpers/getLoggedInUser.js +++ b/app/api/server/helpers/getLoggedInUser.ts @@ -1,17 +1,13 @@ import { Accounts } from 'meteor/accounts-base'; -import { Users } from '../../../models'; +import { Users } from '../../../models/server'; import { API } from '../api'; -API.helperMethods.set('getLoggedInUser', function _getLoggedInUser() { - let user; - +API.helperMethods.set('getLoggedInUser', function _getLoggedInUser(this: any) { if (this.request.headers['x-auth-token'] && this.request.headers['x-user-id']) { - user = Users.findOne({ + return Users.findOne({ '_id': this.request.headers['x-user-id'], 'services.resume.loginTokens.hashedToken': Accounts._hashLoginToken(this.request.headers['x-auth-token']), }); } - - return user; }); diff --git a/app/api/server/helpers/getPaginationItems.js b/app/api/server/helpers/getPaginationItems.ts similarity index 69% rename from app/api/server/helpers/getPaginationItems.js rename to app/api/server/helpers/getPaginationItems.ts index 259f79a1191a..30b5fff786f1 100644 --- a/app/api/server/helpers/getPaginationItems.js +++ b/app/api/server/helpers/getPaginationItems.ts @@ -4,9 +4,12 @@ import { settings } from '../../../settings/server'; import { API } from '../api'; -API.helperMethods.set('getPaginationItems', function _getPaginationItems() { - const hardUpperLimit = settings.get('API_Upper_Count_Limit') <= 0 ? 100 : settings.get('API_Upper_Count_Limit'); - const defaultCount = settings.get('API_Default_Count') <= 0 ? 50 : settings.get('API_Default_Count'); +API.helperMethods.set('getPaginationItems', function _getPaginationItems(this: any) { + const hardUpperLimitTest = settings.get('API_Upper_Count_Limit'); + const defaultCountTest = settings.get('API_Default_Count'); + + const hardUpperLimit = hardUpperLimitTest && hardUpperLimitTest <= 0 ? 100 : settings.get('API_Upper_Count_Limit'); + const defaultCount = defaultCountTest && defaultCountTest <= 0 ? 50 : settings.get('API_Default_Count'); const offset = this.queryParams.offset ? parseInt(this.queryParams.offset) : 0; let count = defaultCount; diff --git a/app/api/server/helpers/getUserFromParams.js b/app/api/server/helpers/getUserFromParams.ts similarity index 94% rename from app/api/server/helpers/getUserFromParams.js rename to app/api/server/helpers/getUserFromParams.ts index 0b562a87b993..a6d57f8f5588 100644 --- a/app/api/server/helpers/getUserFromParams.js +++ b/app/api/server/helpers/getUserFromParams.ts @@ -1,10 +1,10 @@ // Convenience method, almost need to turn it into a middleware of sorts import { Meteor } from 'meteor/meteor'; -import { Users } from '../../../models'; +import { Users } from '../../../models/server'; import { API } from '../api'; -API.helperMethods.set('getUserFromParams', function _getUserFromParams() { +API.helperMethods.set('getUserFromParams', function _getUserFromParams(this: any) { const doesntExist = { _doesntExist: true }; let user; const params = this.requestParams(); @@ -26,7 +26,7 @@ API.helperMethods.set('getUserFromParams', function _getUserFromParams() { return user; }); -API.helperMethods.set('getUserListFromParams', function _getUserListFromParams() { +API.helperMethods.set('getUserListFromParams', function _getUserListFromParams(this: any) { let users; const params = this.requestParams(); // if params.userId is provided, include it as well diff --git a/app/api/server/helpers/getUserInfo.js b/app/api/server/helpers/getUserInfo.js deleted file mode 100644 index 5a1d9b370877..000000000000 --- a/app/api/server/helpers/getUserInfo.js +++ /dev/null @@ -1,62 +0,0 @@ -import { settings } from '../../../settings/server'; -import { getUserPreference, getURL } from '../../../utils/server'; -import { API } from '../api'; - -API.helperMethods.set('getUserInfo', function _getUserInfo(me) { - const isVerifiedEmail = () => { - if (me && me.emails && Array.isArray(me.emails)) { - return me.emails.find((email) => email.verified); - } - return false; - }; - const getUserPreferences = () => { - const defaultUserSettingPrefix = 'Accounts_Default_User_Preferences_'; - const allDefaultUserSettings = settings.getByRegexp(new RegExp(`^${defaultUserSettingPrefix}.*$`)); - - return allDefaultUserSettings.reduce((accumulator, [key]) => { - const settingWithoutPrefix = key.replace(defaultUserSettingPrefix, ' ').trim(); - accumulator[settingWithoutPrefix] = getUserPreference(me, settingWithoutPrefix); - return accumulator; - }, {}); - }; - const verifiedEmail = isVerifiedEmail(); - me.email = verifiedEmail ? verifiedEmail.address : undefined; - - me.avatarUrl = getURL(`/avatar/${me.username}`, { cdn: false, full: true }); - - const userPreferences = (me.settings && me.settings.preferences) || {}; - - me.settings = { - preferences: { - ...getUserPreferences(), - ...userPreferences, - }, - }; - - me.telephoneNumber = ''; - let phoneFieldName = ''; - - settings.get('Contacts_Phone_Custom_Field_Name', function (name, fieldName) { - phoneFieldName = fieldName; - }); - - let phoneFieldArray = []; - if (phoneFieldName) { - phoneFieldArray = phoneFieldName.split(','); - } - - if (phoneFieldArray.length > 0) { - let dict = me; - for (let i = 0; i < phoneFieldArray.length - 1; i++) { - if (phoneFieldArray[i] in dict) { - dict = dict[phoneFieldArray[i]]; - } - } - const phone = dict[phoneFieldArray[phoneFieldArray.length - 1]]; - if (phone) { - me.telephoneNumber = phone; - } - } - - return me; -}); diff --git a/app/api/server/helpers/getUserInfo.ts b/app/api/server/helpers/getUserInfo.ts new file mode 100644 index 000000000000..d58c7ef57fe4 --- /dev/null +++ b/app/api/server/helpers/getUserInfo.ts @@ -0,0 +1,62 @@ +import { IUser, IUserEmail } from '../../../../definition/IUser'; +import { settings } from '../../../settings/server'; +import { getUserPreference, getURL } from '../../../utils/server'; +import { API } from '../api'; + +const isVerifiedEmail = (me: IUser): false | IUserEmail | undefined => { + if (!me || !Array.isArray(me.emails)) { + return false; + } + + return me.emails.find((email) => email.verified); +}; + +const getUserPreferences = (me: IUser): Record => { + const defaultUserSettingPrefix = 'Accounts_Default_User_Preferences_'; + const allDefaultUserSettings = settings.getByRegexp(new RegExp(`^${defaultUserSettingPrefix}.*$`)); + + return allDefaultUserSettings.reduce((accumulator: { [key: string]: unknown }, [key]) => { + const settingWithoutPrefix = key.replace(defaultUserSettingPrefix, ' ').trim(); + accumulator[settingWithoutPrefix] = getUserPreference(me, settingWithoutPrefix); + return accumulator; + }, {}); +}; +API.helperMethods.set('getUserInfo', function _getUserInfo(me: IUser) { + const verifiedEmail = isVerifiedEmail(me); + + const userPreferences = (me.settings && me.settings.preferences) || {}; + + me.telephoneNumber = ''; + const phoneFieldName: string = settings.get('Contacts_Phone_Custom_Field_Name'); + + let phoneFieldArray: string[] = []; + if (phoneFieldName) { + phoneFieldArray = phoneFieldName.split(','); + } + + if (phoneFieldArray.length > 0) { + let dict = me; + for (let i = 0; i < phoneFieldArray.length - 1; i++) { + if (phoneFieldArray[i] in dict) { + dict = (dict as any)[phoneFieldArray[i]]; + } + } + const phone = (dict as any)[phoneFieldArray[phoneFieldArray.length - 1]]; + if (phone) { + me.telephoneNumber = phone; + } + } + + return { + ...me, + email: verifiedEmail ? verifiedEmail.address : undefined, + settings: { + profile: {}, + preferences: { + ...getUserPreferences(me), + ...userPreferences, + }, + }, + avatarUrl: getURL(`/avatar/${me.username}`, { cdn: false, full: true }), + }; +}); diff --git a/app/api/server/helpers/insertUserObject.js b/app/api/server/helpers/insertUserObject.js deleted file mode 100644 index f36cea5275a5..000000000000 --- a/app/api/server/helpers/insertUserObject.js +++ /dev/null @@ -1,16 +0,0 @@ -import { Users } from '../../../models'; -import { API } from '../api'; - -API.helperMethods.set('insertUserObject', function _addUserToObject({ object, userId }) { - const user = Users.findOneById(userId); - object.user = {}; - if (user) { - object.user = { - _id: userId, - username: user.username, - name: user.name, - }; - } - - return object; -}); diff --git a/app/api/server/helpers/insertUserObject.ts b/app/api/server/helpers/insertUserObject.ts new file mode 100644 index 000000000000..2516579cdb6f --- /dev/null +++ b/app/api/server/helpers/insertUserObject.ts @@ -0,0 +1,20 @@ +import { Users } from '../../../models/server'; +import { API } from '../api'; + +API.helperMethods.set( + 'insertUserObject', + function _addUserToObject({ object, userId }: { object: { [key: string]: unknown }; userId: string }) { + // Maybe `object: { [key: string]: Meteor.User }`? + + const user = Users.findOneById(userId); + if (user) { + object.user = { + _id: userId, + username: user.username, + name: user.name, + }; + } + + return object; + }, +); diff --git a/app/api/server/helpers/isUserFromParams.js b/app/api/server/helpers/isUserFromParams.ts similarity index 95% rename from app/api/server/helpers/isUserFromParams.js rename to app/api/server/helpers/isUserFromParams.ts index 16789a9456f5..d32abe05c4e7 100644 --- a/app/api/server/helpers/isUserFromParams.js +++ b/app/api/server/helpers/isUserFromParams.ts @@ -1,6 +1,6 @@ import { API } from '../api'; -API.helperMethods.set('isUserFromParams', function _isUserFromParams() { +API.helperMethods.set('isUserFromParams', function _isUserFromParams(this: any) { const params = this.requestParams(); return ( diff --git a/app/api/server/helpers/isWidget.ts b/app/api/server/helpers/isWidget.ts index 203ab081925d..32d55496dbd6 100644 --- a/app/api/server/helpers/isWidget.ts +++ b/app/api/server/helpers/isWidget.ts @@ -2,7 +2,7 @@ import { parse } from 'cookie'; import { API } from '../api'; -(API as any).helperMethods.set('isWidget', function _isWidget() { +API.helperMethods.set('isWidget', function _isWidget() { // @ts-expect-error const { headers } = this.request; diff --git a/app/api/server/helpers/requestParams.js b/app/api/server/helpers/requestParams.ts similarity index 62% rename from app/api/server/helpers/requestParams.js rename to app/api/server/helpers/requestParams.ts index 2883c94a727e..a68205ab09ed 100644 --- a/app/api/server/helpers/requestParams.js +++ b/app/api/server/helpers/requestParams.ts @@ -1,5 +1,5 @@ import { API } from '../api'; -API.helperMethods.set('requestParams', function _requestParams() { +API.helperMethods.set('requestParams', function _requestParams(this: any) { return ['POST', 'PUT'].includes(this.request.method) ? this.bodyParams : this.queryParams; }); diff --git a/app/api/server/index.js b/app/api/server/index.js index 48d0caa9d6fc..50d9959362b4 100644 --- a/app/api/server/index.js +++ b/app/api/server/index.js @@ -44,5 +44,9 @@ import './v1/instances'; import './v1/banners'; import './v1/email-inbox'; import './v1/teams'; +import './v1/voip/extensions'; +import './v1/voip/queues'; +import './v1/voip/omnichannel'; +import './v1/voip'; export { API, APIClass, defaultRateLimiterOptions } from './api'; diff --git a/app/api/server/lib/getServerInfo.ts b/app/api/server/lib/getServerInfo.ts index 3925aa60a04b..79f60572c56d 100644 --- a/app/api/server/lib/getServerInfo.ts +++ b/app/api/server/lib/getServerInfo.ts @@ -1,5 +1,5 @@ import { Info } from '../../../utils/server'; -import { hasAnyRoleAsync } from '../../../authorization/server/functions/hasRole'; +import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; type ServerInfo = | { @@ -12,7 +12,7 @@ type ServerInfo = const removePatchInfo = (version: string): string => version.replace(/(\d+\.\d+).*/, '$1'); export async function getServerInfo(userId?: string): Promise { - if (userId && (await hasAnyRoleAsync(userId, ['admin']))) { + if (userId && (await hasPermissionAsync(userId, 'get-server-info'))) { return { info: Info, }; diff --git a/app/api/server/lib/rooms.js b/app/api/server/lib/rooms.js index 5516f15676c3..0e402dec3ffe 100644 --- a/app/api/server/lib/rooms.js +++ b/app/api/server/lib/rooms.js @@ -1,34 +1,12 @@ import { hasPermissionAsync, hasAtLeastOnePermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { Rooms } from '../../../models/server/raw'; import { Subscriptions } from '../../../models/server'; +import { adminFields } from '../../../../lib/rooms/adminFields'; export async function findAdminRooms({ uid, filter, types = [], pagination: { offset, count, sort } }) { if (!(await hasPermissionAsync(uid, 'view-room-administration'))) { throw new Error('error-not-authorized'); } - const fields = { - prid: 1, - fname: 1, - name: 1, - t: 1, - cl: 1, - u: 1, - usernames: 1, - usersCount: 1, - muted: 1, - unmuted: 1, - ro: 1, - default: 1, - favorite: 1, - featured: 1, - topic: 1, - msgs: 1, - archived: 1, - tokenpass: 1, - teamId: 1, - teamMain: 1, - }; - const name = filter && filter.trim(); const discussion = types && types.includes('discussions'); const includeTeams = types && types.includes('teams'); @@ -36,7 +14,7 @@ export async function findAdminRooms({ uid, filter, types = [], pagination: { of const typesToRemove = ['discussions', 'teams']; const showTypes = Array.isArray(types) ? types.filter((type) => !typesToRemove.includes(type)) : []; const options = { - fields, + fields: adminFields, sort: sort || { default: -1, name: 1 }, skip: offset, limit: count, @@ -67,30 +45,8 @@ export async function findAdminRoom({ uid, rid }) { if (!(await hasPermissionAsync(uid, 'view-room-administration'))) { throw new Error('error-not-authorized'); } - const fields = { - prid: 1, - fname: 1, - name: 1, - t: 1, - cl: 1, - u: 1, - usernames: 1, - usersCount: 1, - muted: 1, - unmuted: 1, - ro: 1, - default: 1, - favorite: 1, - featured: 1, - topic: 1, - msgs: 1, - archived: 1, - tokenpass: 1, - announcement: 1, - description: 1, - }; - return Rooms.findOneById(rid, { fields }); + return Rooms.findOneById(rid, { fields: adminFields }); } export async function findChannelAndPrivateAutocomplete({ uid, selector }) { diff --git a/app/api/server/v1/channels.js b/app/api/server/v1/channels.js index 24b3da99e45f..371a2de3d1c2 100644 --- a/app/api/server/v1/channels.js +++ b/app/api/server/v1/channels.js @@ -306,13 +306,13 @@ API.v1.addRoute( { authRequired: true }, { post() { - const findResult = findChannelByIdOrName({ + const room = findChannelByIdOrName({ params: this.requestParams(), checkedArchived: false, }); Meteor.runAsUser(this.userId, () => { - Meteor.call('eraseRoom', findResult._id); + Meteor.call('eraseRoom', room._id); }); return API.v1.success(); diff --git a/app/api/server/v1/chat.js b/app/api/server/v1/chat.js index 1d5a9dbb017c..c5f73d8aeab0 100644 --- a/app/api/server/v1/chat.js +++ b/app/api/server/v1/chat.js @@ -3,7 +3,7 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; import { Messages } from '../../../models'; -import { canAccessRoom, hasPermission } from '../../../authorization/server'; +import { canAccessRoom, canAccessRoomId, roomAccessAttributes, hasPermission } from '../../../authorization/server'; import { normalizeMessagesForUser } from '../../../utils/server/lib/normalizeMessagesForUser'; import { processWebhookMessage } from '../../../lib/server'; import { executeSendMessage } from '../../../lib/server/methods/sendMessage'; @@ -510,7 +510,7 @@ API.v1.addRoute( throw new Meteor.Error('error-roomId-param-not-provided', 'The required "roomId" query param is missing.'); } - if (!canAccessRoom({ _id: roomId }, { _id: this.userId })) { + if (!canAccessRoomId(roomId, this.userId)) { throw new Meteor.Error('error-not-allowed', 'Not allowed'); } @@ -549,7 +549,8 @@ API.v1.addRoute( throw new Meteor.Error('error-not-allowed', 'Threads Disabled'); } const user = Users.findOneById(this.userId, { fields: { _id: 1 } }); - const room = Rooms.findOneById(rid, { fields: { t: 1, _id: 1 } }); + const room = Rooms.findOneById(rid, { fields: { ...roomAccessAttributes, t: 1, _id: 1 } }); + if (!canAccessRoom(room, user)) { throw new Meteor.Error('error-not-allowed', 'Not Allowed'); } @@ -557,9 +558,7 @@ API.v1.addRoute( const typeThread = { _hidden: { $ne: true }, ...(type === 'following' && { replies: { $in: [this.userId] } }), - ...(type === 'unread' && { - _id: { $in: Subscriptions.findOneByRoomIdAndUserId(room._id, user._id).tunread }, - }), + ...(type === 'unread' && { _id: { $in: Subscriptions.findOneByRoomIdAndUserId(room._id, user._id).tunread } }), msg: new RegExp(escapeRegExp(text), 'i'), }; @@ -609,7 +608,8 @@ API.v1.addRoute( updatedSinceDate = new Date(updatedSince); } const user = Users.findOneById(this.userId, { fields: { _id: 1 } }); - const room = Rooms.findOneById(rid, { fields: { t: 1, _id: 1 } }); + const room = Rooms.findOneById(rid, { fields: { ...roomAccessAttributes, t: 1, _id: 1 } }); + if (!canAccessRoom(room, user)) { throw new Meteor.Error('error-not-allowed', 'Not Allowed'); } @@ -617,10 +617,7 @@ API.v1.addRoute( return API.v1.success({ threads: { update: Messages.find({ ...threadQuery, _updatedAt: { $gt: updatedSinceDate } }, { fields, sort }).fetch(), - remove: Messages.trashFindDeletedAfter(updatedSinceDate, threadQuery, { - fields, - sort, - }).fetch(), + remove: Messages.trashFindDeletedAfter(updatedSinceDate, threadQuery, { fields, sort }).fetch(), }, }); }, @@ -647,7 +644,7 @@ API.v1.addRoute( throw new Meteor.Error('error-invalid-message', 'Invalid Message'); } const user = Users.findOneById(this.userId, { fields: { _id: 1 } }); - const room = Rooms.findOneById(thread.rid, { fields: { t: 1, _id: 1 } }); + const room = Rooms.findOneById(thread.rid, { fields: { ...roomAccessAttributes, t: 1, _id: 1 } }); if (!canAccessRoom(room, user)) { throw new Meteor.Error('error-not-allowed', 'Not Allowed'); @@ -704,7 +701,7 @@ API.v1.addRoute( throw new Meteor.Error('error-invalid-message', 'Invalid Message'); } const user = Users.findOneById(this.userId, { fields: { _id: 1 } }); - const room = Rooms.findOneById(thread.rid, { fields: { t: 1, _id: 1 } }); + const room = Rooms.findOneById(thread.rid, { fields: { ...roomAccessAttributes, t: 1, _id: 1 } }); if (!canAccessRoom(room, user)) { throw new Meteor.Error('error-not-allowed', 'Not Allowed'); diff --git a/app/api/server/v1/cloud.ts b/app/api/server/v1/cloud.ts index 9ce3d6f02d29..d7e108a7b034 100644 --- a/app/api/server/v1/cloud.ts +++ b/app/api/server/v1/cloud.ts @@ -1,7 +1,7 @@ import { check } from 'meteor/check'; import { API } from '../api'; -import { hasRole, hasPermission } from '../../../authorization/server'; +import { hasPermission, hasRole } from '../../../authorization/server'; import { saveRegistrationData } from '../../../cloud/server/functions/saveRegistrationData'; import { retrieveRegistrationStatus } from '../../../cloud/server/functions/retrieveRegistrationStatus'; import { startRegisterWorkspaceSetupWizard } from '../../../cloud/server/functions/startRegisterWorkspaceSetupWizard'; @@ -16,7 +16,7 @@ API.v1.addRoute( cloudBlob: String, }); - if (!hasRole(this.userId, 'admin')) { + if (!hasPermission(this.userId, 'register-on-cloud')) { return API.v1.unauthorized(); } @@ -90,3 +90,19 @@ API.v1.addRoute( }, }, ); + +API.v1.addRoute( + 'cloud.registrationStatus', + { authRequired: true }, + { + async get() { + if (!hasRole(this.userId, 'admin')) { + return API.v1.unauthorized(); + } + + const registrationStatus = retrieveRegistrationStatus(); + + return API.v1.success({ registrationStatus }); + }, + }, +); diff --git a/app/api/server/v1/commands.js b/app/api/server/v1/commands.js index 0862e2325b00..9a5cde50ef00 100644 --- a/app/api/server/v1/commands.js +++ b/app/api/server/v1/commands.js @@ -4,7 +4,7 @@ import objectPath from 'object-path'; import { slashCommands } from '../../../utils/server'; import { Messages } from '../../../models/server'; -import { canAccessRoom } from '../../../authorization/server'; +import { canAccessRoomId } from '../../../authorization/server'; import { API } from '../api'; API.v1.addRoute( @@ -201,7 +201,7 @@ API.v1.addRoute( return API.v1.failure('The command provided does not exist (or is disabled).'); } - if (!canAccessRoom({ _id: body.roomId }, user)) { + if (!canAccessRoomId(body.roomId, user._id)) { return API.v1.unauthorized(); } @@ -255,7 +255,7 @@ API.v1.addRoute( return API.v1.failure('The command provided does not exist (or is disabled).'); } - if (!canAccessRoom({ _id: query.roomId }, user)) { + if (!canAccessRoomId(query.roomId, user._id)) { return API.v1.unauthorized(); } @@ -310,7 +310,7 @@ API.v1.addRoute( return API.v1.failure('The command provided does not exist (or is disabled).'); } - if (!canAccessRoom({ _id: body.roomId }, user)) { + if (!canAccessRoomId(body.roomId, user._id)) { return API.v1.unauthorized(); } diff --git a/app/api/server/v1/connection.d.ts b/app/api/server/v1/connection.d.ts new file mode 100644 index 000000000000..f822cde6b38f --- /dev/null +++ b/app/api/server/v1/connection.d.ts @@ -0,0 +1,13 @@ +import { IInstanceStatus } from '../../../../definition/IInstanceStatus'; + +export declare const connection: + | { + _id: string; + address: string; + currentStatus: IInstanceStatus['currentStatus']; + instanceRecord: IInstanceStatus['instanceRecord']; + broadcastAuth: boolean; + } + | undefined; + +export as namespace connection; diff --git a/app/api/server/v1/groups.js b/app/api/server/v1/groups.js index a82edb596e67..4a715ad65d2c 100644 --- a/app/api/server/v1/groups.js +++ b/app/api/server/v1/groups.js @@ -5,7 +5,13 @@ import { Match, check } from 'meteor/check'; import { mountIntegrationQueryBasedOnPermissions } from '../../../integrations/server/lib/mountQueriesBasedOnPermission'; import { Subscriptions, Rooms, Messages, Users } from '../../../models/server'; import { Integrations, Uploads } from '../../../models/server/raw'; -import { hasPermission, hasAtLeastOnePermission, canAccessRoom, hasAllPermission } from '../../../authorization/server'; +import { + hasPermission, + hasAtLeastOnePermission, + canAccessRoom, + hasAllPermission, + roomAccessAttributes, +} from '../../../authorization/server'; import { normalizeMessagesForUser } from '../../../utils/server/lib/normalizeMessagesForUser'; import { API } from '../api'; import { Team } from '../../../../server/sdk'; @@ -19,6 +25,7 @@ export function findPrivateGroupByIdOrName({ params, userId, checkedArchived = t const roomOptions = { fields: { + ...roomAccessAttributes, t: 1, ro: 1, name: 1, @@ -640,7 +647,7 @@ API.v1.addRoute( userId: this.userId, }); - if (findResult.broadcast && !hasPermission(this.userId, 'view-broadcast-member-list')) { + if (findResult.broadcast && !hasPermission(this.userId, 'view-broadcast-member-list', findResult.rid)) { return API.v1.unauthorized(); } diff --git a/app/api/server/v1/instances.ts b/app/api/server/v1/instances.ts index 6e097ca53766..f6aabaa19e3f 100644 --- a/app/api/server/v1/instances.ts +++ b/app/api/server/v1/instances.ts @@ -18,6 +18,7 @@ API.v1.addRoute( return API.v1.success({ instances: instances.map((instance: IInstanceStatus) => { const connection = getInstanceConnection(instance); + if (connection) { delete connection.instanceRecord; } diff --git a/app/api/server/v1/ldap.ts b/app/api/server/v1/ldap.ts index 7cdff71126ec..aa4db389d848 100644 --- a/app/api/server/v1/ldap.ts +++ b/app/api/server/v1/ldap.ts @@ -1,6 +1,6 @@ import { Match, check } from 'meteor/check'; -import { hasRole } from '../../../authorization/server'; +import { hasPermission } from '../../../authorization/server'; import { settings } from '../../../settings/server'; import { API } from '../api'; import { SystemLogger } from '../../../../server/lib/logger/system'; @@ -15,7 +15,7 @@ API.v1.addRoute( throw new Error('error-invalid-user'); } - if (!hasRole(this.userId, 'admin')) { + if (!hasPermission(this.userId, 'test-admin-options')) { throw new Error('error-not-authorized'); } @@ -53,7 +53,7 @@ API.v1.addRoute( throw new Error('error-invalid-user'); } - if (!hasRole(this.userId, 'admin')) { + if (!hasPermission(this.userId, 'test-admin-options')) { throw new Error('error-not-authorized'); } diff --git a/app/api/server/v1/roles.ts b/app/api/server/v1/roles.ts index 4f8e309116b1..05cabe20e466 100644 --- a/app/api/server/v1/roles.ts +++ b/app/api/server/v1/roles.ts @@ -7,6 +7,7 @@ import { getUsersInRole, hasRole } from '../../../authorization/server'; import { settings } from '../../../settings/server/index'; import { api } from '../../../../server/sdk/api'; import { Roles } from '../../../models/server/raw'; +import { apiDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { hasAnyRoleAsync } from '../../../authorization/server/functions/hasRole'; import { isRoleAddUserToRoleProps, @@ -16,6 +17,9 @@ import { isRoleUpdateProps, } from '../../../../definition/rest/v1/roles'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; +import { updateRole } from '../../../../server/lib/roles/updateRole'; +import { insertRole } from '../../../../server/lib/roles/insertRole'; +import type { IRole } from '../../../../definition/IRole'; API.v1.addRoute( 'roles.list', @@ -62,38 +66,31 @@ API.v1.addRoute( throw new Meteor.Error('error-invalid-role-properties', 'The role properties are invalid.'); } - const { name, scope, description, mandatory2fa } = this.bodyParams; + const userId = Meteor.userId(); - if (!(await hasPermissionAsync(Meteor.userId(), 'access-permissions'))) { + if (!userId || !(await hasPermissionAsync(userId, 'access-permissions'))) { throw new Meteor.Error('error-action-not-allowed', 'Accessing permissions is not allowed'); } + const { name, scope, description, mandatory2fa } = this.bodyParams; + if (await Roles.findOneByIdOrName(name)) { throw new Meteor.Error('error-duplicate-role-names-not-allowed', 'Role name already exists'); } - const roleId = ( - await Roles.createWithRandomId( - name, - scope && ['Users', 'Subscriptions'].includes(scope) ? scope : 'Users', - description, - false, - mandatory2fa, - ) - ).insertedId; - - if (settings.get('UI_DisplayRoles')) { - api.broadcast('user.roleUpdate', { - type: 'changed', - _id: roleId, - }); - } + const roleData = { + description: description || '', + ...(mandatory2fa !== undefined && { mandatory2fa }), + name, + scope: scope || 'Users', + protected: false, + }; - const role = await Roles.findOneByIdOrName(roleId); + const options = { + broadcastUpdate: settings.get('UI_DisplayRoles'), + }; - if (!role) { - return API.v1.failure('error-role-not-found', 'Role not found'); - } + const role = insertRole(roleData, options); return API.v1.success({ role, @@ -112,20 +109,27 @@ API.v1.addRoute( } const user = this.getUserFromParams(); - const { roleName, roomId } = this.bodyParams; + const { roleId, roleName, roomId } = this.bodyParams; - if (hasRole(user._id, roleName, roomId)) { - throw new Meteor.Error('error-user-already-in-role', 'User already in role'); - } - - await Meteor.call('authorization:addUserToRole', roleName, user.username, roomId); + if (!roleId) { + if (!roleName) { + return API.v1.failure('error-invalid-role-properties'); + } - const role = await Roles.findOneByIdOrName(roleName); + apiDeprecationLogger.warn(`Assigning roles by name is deprecated and will be removed on the next major release of Rocket.Chat`); + } + const role = roleId ? await Roles.findOneById(roleId) : await Roles.findOneByIdOrName(roleName as string); if (!role) { return API.v1.failure('error-role-not-found', 'Role not found'); } + if (hasRole(user._id, role._id, roomId)) { + throw new Meteor.Error('error-user-already-in-role', 'User already in role'); + } + + await Meteor.call('authorization:addUserToRole', role._id, user.username, roomId); + return API.v1.success({ role, }); @@ -157,7 +161,19 @@ API.v1.addRoute( if (roomId && !(await hasPermissionAsync(this.userId, 'view-other-user-channels'))) { throw new Meteor.Error('error-not-allowed', 'Not allowed'); } - const users = await getUsersInRole(role, roomId, { + + const options = { projection: { _id: 1 } }; + let roleData = await Roles.findOneById>(role, options); + if (!roleData) { + roleData = await Roles.findOneByName>(role, options); + if (!roleData) { + throw new Meteor.Error('error-invalid-roleId'); + } + + apiDeprecationLogger.warn(`Querying roles by name is deprecated and will be removed on the next major release of Rocket.Chat`); + } + + const users = await getUsersInRole(roleData._id, roomId, { limit: count as number, sort: { username: 1 }, skip: offset as number, @@ -174,8 +190,7 @@ API.v1.addRoute( { authRequired: true }, { async post() { - const { bodyParams } = this; - if (!isRoleUpdateProps(bodyParams)) { + if (!isRoleUpdateProps(this.bodyParams)) { throw new Meteor.Error('error-invalid-role-properties', 'The role properties are invalid.'); } @@ -183,52 +198,24 @@ API.v1.addRoute( throw new Meteor.Error('error-action-not-allowed', 'Accessing permissions is not allowed'); } + const { roleId, name, scope, description, mandatory2fa } = this.bodyParams; + const roleData = { - roleId: bodyParams.roleId, - name: bodyParams.name, - scope: bodyParams.scope || 'Users', - description: bodyParams.description, - mandatory2fa: bodyParams.mandatory2fa, + description: description || '', + ...(mandatory2fa !== undefined && { mandatory2fa }), + name, + scope: scope || 'Users', + protected: false, }; - const role = await Roles.findOneByIdOrName(roleData.roleId); - - if (!role) { - throw new Meteor.Error('error-invalid-roleId', 'This role does not exist'); - } - - if (role.protected && ((roleData.name && roleData.name !== role.name) || (roleData.scope && roleData.scope !== role.scope))) { - throw new Meteor.Error('error-role-protected', 'Role is protected'); - } - - if (roleData.name) { - const otherRole = await Roles.findOneByIdOrName(roleData.name); - if (otherRole && otherRole._id !== role._id) { - throw new Meteor.Error('error-duplicate-role-names-not-allowed', 'Role name already exists'); - } - } - - if (['Users', 'Subscriptions'].includes(roleData.scope) === false) { - throw new Meteor.Error('error-invalid-scope', 'Invalid scope'); - } - - await Roles.updateById(roleData.roleId, roleData.name, roleData.scope, roleData.description, roleData.mandatory2fa); - - if (settings.get('UI_DisplayRoles')) { - api.broadcast('user.roleUpdate', { - type: 'changed', - _id: roleData.roleId, - }); - } + const options = { + broadcastUpdate: settings.get('UI_DisplayRoles'), + }; - const updatedRole = await Roles.findOneByIdOrName(roleData.roleId); - - if (!updatedRole) { - return API.v1.failure(); - } + const role = updateRole(roleId, roleData, options); return API.v1.success({ - role: updatedRole, + role, }); }, }, @@ -258,7 +245,7 @@ API.v1.addRoute( throw new Meteor.Error('error-role-protected', 'Cannot delete a protected role'); } - const existingUsers = await Roles.findUsersInRole(role.name, role.scope); + const existingUsers = await Roles.findUsersInRole(role._id); if (existingUsers && (await existingUsers.count()) > 0) { throw new Meteor.Error('error-role-in-use', "Cannot delete role because it's in use"); @@ -281,25 +268,33 @@ API.v1.addRoute( throw new Meteor.Error('error-invalid-role-properties', 'The role properties are invalid.'); } - const { roleName, username, scope } = bodyParams; + const { roleId, roleName, username, scope } = bodyParams; if (!(await hasPermissionAsync(this.userId, 'access-permissions'))) { throw new Meteor.Error('error-not-allowed', 'Accessing permissions is not allowed'); } + if (!roleId) { + if (!roleName) { + return API.v1.failure('error-invalid-role-properties'); + } + + apiDeprecationLogger.warn(`Unassigning roles by name is deprecated and will be removed on the next major release of Rocket.Chat`); + } + const user = Users.findOneByUsername(username); if (!user) { throw new Meteor.Error('error-invalid-user', 'There is no user with this username'); } - const role = await Roles.findOneByIdOrName(roleName); + const role = roleId ? await Roles.findOneById(roleId) : await Roles.findOneByIdOrName(roleName as string); if (!role) { throw new Meteor.Error('error-invalid-roleId', 'This role does not exist'); } - if (!(await hasAnyRoleAsync(user._id, [role.name], scope))) { + if (!(await hasAnyRoleAsync(user._id, [role._id], scope))) { throw new Meteor.Error('error-user-not-in-role', 'User is not in this role'); } @@ -310,7 +305,7 @@ API.v1.addRoute( } } - await Roles.removeUserRoles(user._id, [role.name], scope); + await Roles.removeUserRoles(user._id, [role._id], scope); if (settings.get('UI_DisplayRoles')) { api.broadcast('user.roleUpdate', { diff --git a/app/api/server/v1/rooms.js b/app/api/server/v1/rooms.js index 30e815c9a361..32eefbcd8c8e 100644 --- a/app/api/server/v1/rooms.js +++ b/app/api/server/v1/rooms.js @@ -12,7 +12,7 @@ import { findChannelAndPrivateAutocompleteWithPagination, } from '../lib/rooms'; import { sendFile, sendViaEmail } from '../../../../server/lib/channelExport'; -import { canAccessRoom, hasPermission } from '../../../authorization/server'; +import { canAccessRoom, canAccessRoomId, hasPermission } from '../../../authorization/server'; import { Media } from '../../../../server/sdk'; import { settings } from '../../../settings/server/index'; import { getUploadFormData } from '../lib/getUploadFormData'; @@ -81,7 +81,7 @@ API.v1.addRoute( { authRequired: true }, { post() { - if (!canAccessRoom({ _id: this.urlParams.rid }, { _id: this.userId })) { + if (!canAccessRoomId(this.urlParams.rid, this.userId)) { return API.v1.unauthorized(); } @@ -525,18 +525,23 @@ API.v1.addRoute( } if (type === 'file') { - const { dateFrom, dateTo, format } = this.bodyParams; + let { dateFrom, dateTo } = this.bodyParams; + const { format } = this.bodyParams; if (!['html', 'json'].includes(format)) { throw new Meteor.Error('error-invalid-format'); } + dateFrom = new Date(dateFrom); + dateTo = new Date(dateTo); + dateTo.setDate(dateTo.getDate() + 1); + sendFile( { rid, format, - ...(dateFrom && { dateFrom: new Date(dateFrom) }), - ...(dateTo && { dateTo: new Date(dateTo) }), + dateFrom, + dateTo, }, user, ); diff --git a/app/api/server/v1/stats.js b/app/api/server/v1/stats.js index c010cc3090ab..2b86338d7316 100644 --- a/app/api/server/v1/stats.js +++ b/app/api/server/v1/stats.js @@ -1,5 +1,6 @@ import { API } from '../api'; import { getStatistics, getLastStatistics } from '../../../statistics/server'; +import telemetryEvent from '../../../statistics/server/lib/telemetryEvents'; API.v1.addRoute( 'statistics', @@ -44,3 +45,20 @@ API.v1.addRoute( }, }, ); + +API.v1.addRoute( + 'statistics.telemetry', + { authRequired: true }, + { + post() { + const events = this.requestParams(); + + events.params.forEach((event) => { + const { eventName, ...params } = event; + telemetryEvent.call(eventName, params); + }); + + return API.v1.success(); + }, + }, +); diff --git a/app/api/server/v1/teams.ts b/app/api/server/v1/teams.ts index 4407071d31cc..509085a4657a 100644 --- a/app/api/server/v1/teams.ts +++ b/app/api/server/v1/teams.ts @@ -140,7 +140,11 @@ API.v1.addRoute( }); } - await Promise.all([Team.unsetTeamIdOfRooms(team._id), Team.removeAllMembersFromTeam(team._id), Team.deleteById(team._id)]); + await Promise.all([ + Team.unsetTeamIdOfRooms(this.userId, team._id), + Team.removeAllMembersFromTeam(team._id), + Team.deleteById(team._id), + ]); return API.v1.success(); }, @@ -333,7 +337,7 @@ API.v1.addRoute( this.queryParams, Match.ObjectIncluding({ userId: String, - canUserDelete: Match.Maybe(Boolean), + canUserDelete: Match.Maybe(String), }), ); @@ -352,7 +356,8 @@ API.v1.addRoute( return API.v1.unauthorized(); } - const { records, total } = await Team.listRoomsOfUser(this.userId, team._id, userId, allowPrivateTeam, canUserDelete ?? false, { + const booleanCanUserDelete = canUserDelete === 'true'; + const { records, total } = await Team.listRoomsOfUser(this.userId, team._id, userId, allowPrivateTeam, booleanCanUserDelete, { offset, count, }); @@ -511,11 +516,13 @@ API.v1.addRoute( if (rooms?.length) { const roomsFromTeam: string[] = await Team.getMatchingTeamRooms(team._id, rooms); - roomsFromTeam.forEach((rid) => { - removeUserFromRoom(rid, user, { - byUser: this.user, - }); - }); + await Promise.all( + roomsFromTeam.map((rid) => + removeUserFromRoom(rid, user, { + byUser: this.user, + }), + ), + ); } return API.v1.success(); }, @@ -546,10 +553,7 @@ API.v1.addRoute( if (rooms.length) { const roomsFromTeam: string[] = await Team.getMatchingTeamRooms(team._id, rooms); - - roomsFromTeam.forEach((rid) => { - removeUserFromRoom(rid, this.user); - }); + await Promise.all(roomsFromTeam.map((rid) => removeUserFromRoom(rid, this.user))); } return API.v1.success(); @@ -606,9 +610,6 @@ API.v1.addRoute( const rooms: string[] = await Team.getMatchingTeamRooms(team._id, roomsToRemove); - // Remove the team's main room - Meteor.call('eraseRoom', team.roomId); - // If we got a list of rooms to delete along with the team, remove them first if (rooms.length) { rooms.forEach((room) => { @@ -617,7 +618,10 @@ API.v1.addRoute( } // Move every other room back to the workspace - await Team.unsetTeamIdOfRooms(team._id); + await Team.unsetTeamIdOfRooms(this.userId, team._id); + + // Remove the team's main room + Meteor.call('eraseRoom', team.roomId); // Delete all team memberships Team.removeAllMembersFromTeam(team._id); diff --git a/app/api/server/v1/voip/events.ts b/app/api/server/v1/voip/events.ts new file mode 100644 index 000000000000..76b70ee1a71e --- /dev/null +++ b/app/api/server/v1/voip/events.ts @@ -0,0 +1,35 @@ +import { Match, check } from 'meteor/check'; + +import { API } from '../../api'; +import { LivechatVoip } from '../../../../../server/sdk'; +import { canAccessRoom } from '../../../../authorization/server'; +import { VoipRoom } from '../../../../models/server/raw'; +import { VoipClientEvents } from '../../../../../definition/voip/VoipClientEvents'; + +API.v1.addRoute( + 'voip/events', + { authRequired: true }, + { + async post() { + check(this.requestParams(), { + event: Match.Where((v: string) => { + return Object.values(VoipClientEvents).includes(v); + }), + rid: String, + comment: Match.Maybe(String), + }); + + const { rid, event, comment } = this.requestParams(); + + const room = await VoipRoom.findOneVoipRoomById(rid); + if (!room) { + return API.v1.notFound(); + } + if (!canAccessRoom(room, this.user)) { + return API.v1.unauthorized(); + } + + return API.v1.success(await LivechatVoip.handleEvent(event, room, this.user, comment)); + }, + }, +); diff --git a/app/api/server/v1/voip/extensions.ts b/app/api/server/v1/voip/extensions.ts new file mode 100644 index 000000000000..bc718bd6cd40 --- /dev/null +++ b/app/api/server/v1/voip/extensions.ts @@ -0,0 +1,125 @@ +import { Match, check } from 'meteor/check'; + +import { API } from '../../api'; +import { Users } from '../../../../models/server/raw/index'; +import { Voip } from '../../../../../server/sdk'; +import { IVoipExtensionBase } from '../../../../../definition/IVoipExtension'; +import { generateJWT } from '../../../../utils/server/lib/JWTHelper'; +import { settings } from '../../../../settings/server'; +import { logger } from './logger'; + +// Get the connector version and type +API.v1.addRoute( + 'connector.getVersion', + { authRequired: true, permissionsRequired: ['manage-voip-call-settings'] }, + { + async get() { + const version = await Voip.getConnectorVersion(); + return API.v1.success(version); + }, + }, +); + +// Get the extensions available on the call server +API.v1.addRoute( + 'connector.extension.list', + { authRequired: true, permissionsRequired: ['manage-voip-call-settings'] }, + { + async get() { + const list = await Voip.getExtensionList(); + const result = list.result as IVoipExtensionBase[]; + return API.v1.success({ extensions: result }); + }, + }, +); + +/* Get the details of a single extension. + * Note : This API will either be called by the endpoint + * or will be consumed internally. + */ +API.v1.addRoute( + 'connector.extension.getDetails', + { authRequired: true, permissionsRequired: ['manage-voip-call-settings'] }, + { + async get() { + check( + this.requestParams(), + Match.ObjectIncluding({ + extension: String, + }), + ); + const endpointDetails = await Voip.getExtensionDetails(this.requestParams()); + return API.v1.success({ ...endpointDetails.result }); + }, + }, +); + +/* Get the details for registration extension. + */ + +API.v1.addRoute( + 'connector.extension.getRegistrationInfoByExtension', + { authRequired: true, permissionsRequired: ['manage-voip-call-settings'] }, + { + async get() { + check( + this.requestParams(), + Match.ObjectIncluding({ + extension: String, + }), + ); + const endpointDetails = await Voip.getRegistrationInfo(this.requestParams()); + const encKey = settings.get('VoIP_JWT_Secret'); + if (!encKey) { + logger.warn('No JWT keys set. Sending registration info as plain text'); + return API.v1.success({ ...endpointDetails.result }); + } + + const result = generateJWT(endpointDetails.result, encKey); + return API.v1.success({ result }); + }, + }, +); + +API.v1.addRoute( + 'connector.extension.getRegistrationInfoByUserId', + { authRequired: true, permissionsRequired: ['view-agent-extension-association'] }, + { + async get() { + check( + this.requestParams(), + Match.ObjectIncluding({ + id: String, + }), + ); + const { id } = this.requestParams(); + + if (id !== this.userId) { + return API.v1.unauthorized(); + } + + const { extension } = + (await Users.getVoipExtensionByUserId(id, { + projection: { + _id: 1, + username: 1, + extension: 1, + }, + })) || {}; + + if (!extension) { + return API.v1.notFound('Extension not found'); + } + + const endpointDetails = await Voip.getRegistrationInfo({ extension }); + const encKey = settings.get('VoIP_JWT_Secret'); + if (!encKey) { + logger.warn('No JWT keys set. Sending registration info as plain text'); + return API.v1.success({ ...endpointDetails.result }); + } + + const result = generateJWT(endpointDetails.result, encKey); + return API.v1.success({ result }); + }, + }, +); diff --git a/app/api/server/v1/voip/index.ts b/app/api/server/v1/voip/index.ts new file mode 100644 index 000000000000..95b8dd15a938 --- /dev/null +++ b/app/api/server/v1/voip/index.ts @@ -0,0 +1,5 @@ +import './extensions'; +import './queues'; +import './events'; +import './rooms'; +import './server-connection'; diff --git a/app/api/server/v1/voip/logger.ts b/app/api/server/v1/voip/logger.ts new file mode 100644 index 000000000000..792cf79427ea --- /dev/null +++ b/app/api/server/v1/voip/logger.ts @@ -0,0 +1,3 @@ +import { Logger } from '../../../../logger/server'; + +export const logger = new Logger('VoIP'); diff --git a/app/api/server/v1/voip/omnichannel.ts b/app/api/server/v1/voip/omnichannel.ts new file mode 100644 index 000000000000..a84831de87c1 --- /dev/null +++ b/app/api/server/v1/voip/omnichannel.ts @@ -0,0 +1,239 @@ +import { Match, check } from 'meteor/check'; + +import { API } from '../../api'; +import { Users } from '../../../../models/server/raw/index'; +import { hasPermission } from '../../../../authorization/server/index'; +import { LivechatVoip } from '../../../../../server/sdk'; +import { logger } from './logger'; +import { IUser } from '../../../../../definition/IUser'; + +function paginate(array: T[], count = 10, offset = 0): T[] { + return array.slice(offset, offset + count); +} + +const isUserAndExtensionParams = (p: any): p is { userId: string; extension: string } => p.userId && p.extension; +const isUserIdndTypeParams = (p: any): p is { userId: string; type: 'free' | 'allocated' | 'available' } => p.userId && p.type; + +API.v1.addRoute( + 'omnichannel/agent/extension', + { authRequired: true }, + { + // Get the extensions associated with the agent passed as request params. + async get() { + if (!hasPermission(this.userId, 'view-agent-extension-association')) { + return API.v1.unauthorized(); + } + check( + this.requestParams(), + Match.ObjectIncluding({ + username: String, + }), + ); + const { username } = this.requestParams(); + const user = await Users.findOneByAgentUsername(username, { + projection: { _id: 1 }, + }); + if (!user) { + return API.v1.notFound('User not found'); + } + const extension = await Users.getVoipExtensionByUserId(user._id, { + projection: { + _id: 1, + username: 1, + extension: 1, + }, + }); + if (!extension) { + return API.v1.notFound('Extension not found'); + } + return API.v1.success({ extension }); + }, + + // Create agent-extension association. + async post() { + if (!hasPermission(this.userId, 'manage-agent-extension-association')) { + return API.v1.unauthorized(); + } + check( + this.bodyParams, + Match.OneOf( + Match.ObjectIncluding({ + username: String, + extension: String, + }), + Match.ObjectIncluding({ + userId: String, + extension: String, + }), + ), + ); + + const { extension } = this.bodyParams; + let user: IUser | null = null; + + if (!isUserAndExtensionParams(this.bodyParams)) { + if (!this.bodyParams.username) { + return API.v1.notFound(); + } + user = await Users.findOneByAgentUsername(this.bodyParams.username, { + projection: { + _id: 1, + username: 1, + }, + }); + } else { + if (!this.bodyParams.userId) { + return API.v1.notFound(); + } + user = await Users.findOneAgentById(this.bodyParams.userId, { + projection: { + _id: 1, + username: 1, + }, + }); + } + + if (!user) { + return API.v1.notFound(); + } + + try { + logger.debug(`Setting extension ${extension} for agent with id ${user._id}`); + await Users.setExtension(user._id, extension); + return API.v1.success(); + } catch (e) { + logger.error({ msg: 'Extension already in use' }); + return API.v1.failure(`extension already in use ${extension}`); + } + }, + + async delete() { + if (!hasPermission(this.userId, 'manage-agent-extension-association')) { + return API.v1.unauthorized(); + } + check( + this.requestParams(), + Match.ObjectIncluding({ + username: String, + }), + ); + const { username } = this.requestParams(); + const user = await Users.findOneByAgentUsername(username, { + projection: { + _id: 1, + username: 1, + extension: 1, + }, + }); + if (!user) { + return API.v1.notFound(); + } + if (!user.extension) { + logger.debug(`User ${user._id} is not associated with any extension. Skipping`); + return API.v1.success(); + } + + logger.debug(`Removing extension association for user ${user._id} (extension was ${user.extension})`); + await Users.unsetExtension(user._id); + return API.v1.success(); + }, + }, +); + +// Get free extensions +API.v1.addRoute( + 'omnichannel/extension', + { authRequired: true, permissionsRequired: ['manage-agent-extension-association'] }, + { + async get() { + check( + this.queryParams, + Match.OneOf( + Match.ObjectIncluding({ + type: Match.OneOf('free', 'allocated', 'available'), + userId: String, + }), + Match.ObjectIncluding({ + type: Match.OneOf('free', 'allocated', 'available'), + username: String, + }), + ), + ); + const { type } = this.queryParams; + switch ((type as string).toLowerCase()) { + case 'free': { + const extensions = await LivechatVoip.getFreeExtensions(); + if (!extensions) { + return API.v1.failure('Error in finding free extensons'); + } + return API.v1.success({ extensions }); + } + case 'allocated': { + const extensions = await LivechatVoip.getExtensionAllocationDetails(); + if (!extensions) { + return API.v1.failure('Error in allocated extensions'); + } + return API.v1.success({ extensions: extensions.map((e) => e.extension) }); + } + case 'available': { + let user: IUser | null = null; + if (!isUserIdndTypeParams(this.queryParams)) { + user = await Users.findOneByAgentUsername(this.queryParams.username, { + projection: { _id: 1, extension: 1 }, + }); + } else { + user = await Users.findOneAgentById(this.queryParams.userId, { + projection: { _id: 1, extension: 1 }, + }); + } + + const freeExt = await LivechatVoip.getFreeExtensions(); + const extensions = user?.extension ? [user.extension, ...freeExt] : freeExt; + return API.v1.success({ extensions }); + } + default: + return API.v1.notFound(`${type} not found `); + } + }, + }, +); + +API.v1.addRoute( + 'omnichannel/extensions', + { authRequired: true, permissionsRequired: ['manage-agent-extension-association'] }, + { + async get() { + const { offset, count } = this.getPaginationItems(); + const extensions = await LivechatVoip.getExtensionListWithAgentData(); + + // paginating in memory as Asterisk doesn't provide pagination for commands + return API.v1.success({ + extensions: paginate(extensions, count, offset), + offset, + count, + total: extensions.length, + }); + }, + }, +); + +API.v1.addRoute( + 'omnichannel/agents/available', + { authRequired: true, permissionsRequired: ['manage-agent-extension-association'] }, + { + async get() { + const { offset, count } = this.getPaginationItems(); + const { sort } = this.parseJsonQuery(); + const { text, includeExtension = '' } = this.queryParams; + + const { agents, total } = await LivechatVoip.getAvailableAgents(includeExtension, text, count, offset, sort); + + return API.v1.success({ + agents, + offset, + count, + total, + }); + }, + }, +); diff --git a/app/api/server/v1/voip/queues.ts b/app/api/server/v1/voip/queues.ts new file mode 100644 index 000000000000..a1f027c37ce9 --- /dev/null +++ b/app/api/server/v1/voip/queues.ts @@ -0,0 +1,52 @@ +import { Match, check } from 'meteor/check'; + +import { API } from '../../api'; +import { Voip } from '../../../../../server/sdk'; +import { IVoipConnectorResult } from '../../../../../definition/IVoipConnectorResult'; +import { IQueueSummary } from '../../../../../definition/ACDQueues'; +import { IQueueMembershipDetails, IQueueMembershipSubscription } from '../../../../../definition/IVoipExtension'; + +API.v1.addRoute( + 'voip/queues.getSummary', + { authRequired: true }, + { + async get() { + const queueSummary = await Voip.getQueueSummary(); + return API.v1.success({ summary: queueSummary.result as IQueueSummary[] }); + }, + }, +); + +API.v1.addRoute( + 'voip/queues.getQueuedCallsForThisExtension', + { authRequired: true }, + { + async get() { + check( + this.requestParams(), + Match.ObjectIncluding({ + extension: String, + }), + ); + const membershipDetails: IVoipConnectorResult = await Voip.getQueuedCallsForThisExtension(this.requestParams()); + return API.v1.success(membershipDetails.result as IQueueMembershipDetails); + }, + }, +); + +API.v1.addRoute( + 'voip/queues.getMembershipSubscription', + { authRequired: true }, + { + async get() { + check( + this.requestParams(), + Match.ObjectIncluding({ + extension: String, + }), + ); + const membershipDetails: IVoipConnectorResult = await Voip.getQueueMembership(this.requestParams()); + return API.v1.success(membershipDetails.result as IQueueMembershipSubscription); + }, + }, +); diff --git a/app/api/server/v1/voip/rooms.ts b/app/api/server/v1/voip/rooms.ts new file mode 100644 index 000000000000..323524c6d565 --- /dev/null +++ b/app/api/server/v1/voip/rooms.ts @@ -0,0 +1,244 @@ +import { Match, check } from 'meteor/check'; +import { Random } from 'meteor/random'; + +import { API } from '../../api'; +import { VoipRoom, LivechatVisitors, Users } from '../../../../models/server/raw'; +import { LivechatVoip } from '../../../../../server/sdk'; +import { ILivechatAgent } from '../../../../../definition/ILivechatAgent'; +import { hasPermission } from '../../../../authorization/server'; +import { typedJsonParse } from '../../../../../lib/typedJSONParse'; + +type DateParam = { start?: string; end?: string }; +const parseDateParams = (date?: string): DateParam => { + return date && typeof date === 'string' ? typedJsonParse(date) : {}; +}; +const validateDateParams = (property: string, date: DateParam = {}): DateParam => { + if (date?.start && isNaN(Date.parse(date.start))) { + throw new Error(`The "${property}.start" query parameter must be a valid date.`); + } + if (date?.end && isNaN(Date.parse(date.end))) { + throw new Error(`The "${property}.end" query parameter must be a valid date.`); + } + return date; +}; +const parseAndValidate = (property: string, date?: string): DateParam => { + return validateDateParams(property, parseDateParams(date)); +}; +/** + * @openapi + * /voip/server/api/v1/voip/room + * get: + * description: Creates a new room if rid is not passed, else gets an existing room + * based on rid and token . This configures the rate limit. An average call volume in a contact + * center is 600 calls a day + * considering 8 hour shift. Which comes to 1.25 calls per minute. + * we will keep the safe limit which is 5 calls a minute. + * security: + * parameters: + * - name: token + * in: query + * description: The visitor token + * required: true + * schema: + * type: string + * example: ByehQjC44FwMeiLbX + * - name: rid + * in: query + * description: The room id + * required: false + * schema: + * type: string + * example: ByehQjC44FwMeiLbX + * - name: agentId + * in: query + * description: Agent Id + * required: false + * schema: + * type: string + * example: ByehQjC44FwMeiLbX + * responses: + * 200: + * description: Room object and flag indicating whether a new room is created. + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiSuccessV1' + * - type: object + * properties: + * room: + * type: object + * items: + * $ref: '#/components/schemas/IRoom' + * newRoom: + * type: boolean + * default: + * description: Unexpected error + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiFailureV1' + */ + +API.v1.addRoute( + 'voip/room', + { authRequired: false, rateLimiterOptions: { numRequestsAllowed: 5, intervalTimeInMS: 60000 } }, + { + async get() { + const defaultCheckParams = { + token: String, + agentId: Match.Maybe(String), + rid: Match.Maybe(String), + }; + check(this.queryParams, defaultCheckParams); + + const { token, rid, agentId } = this.queryParams; + const guest = await LivechatVisitors.getVisitorByToken(token, {}); + if (!guest) { + return API.v1.failure('invalid-token'); + } + + if (!rid) { + const room = await VoipRoom.findOneOpenByVisitorToken(token, { projection: API.v1.defaultFieldsToExclude }); + if (room) { + return API.v1.success({ room, newRoom: false }); + } + + const agentObj: ILivechatAgent = await Users.findOneAgentById(agentId, { + projection: { username: 1 }, + }); + if (!agentObj?.username) { + return API.v1.failure('agent-not-found'); + } + + const { username, _id } = agentObj; + const agent = { agentId: _id, username }; + const rid = Random.id(); + + return API.v1.success(await LivechatVoip.getNewRoom(guest, agent, rid, { projection: API.v1.defaultFieldsToExclude })); + } + + const room = await VoipRoom.findOneByIdAndVisitorToken(rid, token, { projection: API.v1.defaultFieldsToExclude }); + if (!room) { + return API.v1.failure('invalid-room'); + } + return API.v1.success({ room, newRoom: false }); + }, + }, +); + +API.v1.addRoute( + 'voip/rooms', + { authRequired: true }, + { + async get() { + const { offset, count } = this.getPaginationItems(); + const { sort, fields } = this.parseJsonQuery(); + const { agents, open, tags, queue, visitorId } = this.requestParams(); + const { createdAt: createdAtParam, closedAt: closedAtParam } = this.requestParams(); + + check(agents, Match.Maybe([String])); + check(open, Match.Maybe(String)); + check(tags, Match.Maybe([String])); + check(queue, Match.Maybe(String)); + check(visitorId, Match.Maybe(String)); + + // Reusing same L room permissions for simplicity + const hasAdminAccess = hasPermission(this.userId, 'view-livechat-rooms'); + const hasAgentAccess = hasPermission(this.userId, 'view-l-room') && agents?.includes(this.userId) && agents?.length === 1; + if (!hasAdminAccess && !hasAgentAccess) { + return API.v1.unauthorized(); + } + + const createdAt = parseAndValidate('createdAt', createdAtParam); + const closedAt = parseAndValidate('closedAt', closedAtParam); + + return API.v1.success( + await LivechatVoip.findVoipRooms({ + agents, + open: open === 'true', + tags, + queue, + visitorId, + createdAt, + closedAt, + options: { sort, offset, count, fields }, + }), + ); + }, + }, +); + +/** + * @openapi + * /voip/server/api/v1/voip/room.close + * post: + * description: Closes an open room + * based on rid and token. Setting rate limit for this too + * Because room creation happens 5/minute, rate limit for this api + * is also set to 5/minute. + * security: + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * rid: + * type: string + * token: + * type: string + * responses: + * 200: + * description: rid of closed room and a comment for closing room + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/ApiSuccessV1' + * - type: object + * properties: + * rid: + * type: string + * comment: + * type: string + * default: + * description: Unexpected error + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ApiFailureV1' + */ +API.v1.addRoute( + 'voip/room.close', + { authRequired: true }, + { + async post() { + check(this.bodyParams, { + rid: String, + token: String, + comment: Match.Maybe(String), + tags: Match.Maybe([String]), + }); + const { rid, token, comment, tags } = this.bodyParams; + + const visitor = await LivechatVisitors.getVisitorByToken(token, {}); + if (!visitor) { + return API.v1.failure('invalid-token'); + } + const room = await LivechatVoip.findRoom(token, rid); + if (!room) { + return API.v1.failure('invalid-room'); + } + if (!room.open) { + return API.v1.failure('room-closed'); + } + const closeResult = await LivechatVoip.closeRoom(visitor, room, this.user, comment, tags); + if (!closeResult) { + return API.v1.failure(); + } + return API.v1.success({ rid }); + }, + }, +); diff --git a/app/api/server/v1/voip/server-connection.ts b/app/api/server/v1/voip/server-connection.ts new file mode 100644 index 000000000000..d784a87e95f1 --- /dev/null +++ b/app/api/server/v1/voip/server-connection.ts @@ -0,0 +1,59 @@ +import { Match, check } from 'meteor/check'; + +import { API } from '../../api'; +import { Voip } from '../../../../../server/sdk'; + +API.v1.addRoute( + 'voip/managementServer/checkConnection', + { authRequired: true, permissionsRequired: ['manage-voip-contact-center-settings'] }, + { + async get() { + check( + this.requestParams(), + Match.ObjectIncluding({ + host: String, + port: String, + username: String, + password: String, + }), + ); + const { host, port, username, password } = this.requestParams(); + return API.v1.success(await Voip.checkManagementConnection(host, port, username, password)); + }, + }, +); + +API.v1.addRoute( + 'voip/callServer/checkConnection', + { authRequired: true, permissionsRequired: ['manage-voip-contact-center-settings'] }, + { + async get() { + check( + this.requestParams(), + Match.ObjectIncluding({ + websocketUrl: Match.Maybe(String), + host: Match.Maybe(String), + port: Match.Maybe(String), + path: Match.Maybe(String), + }), + ); + const { websocketUrl, host, port, path } = this.requestParams(); + if (!websocketUrl && !(host && port && path)) { + return API.v1.failure('Incorrect / Insufficient Parameters'); + } + let socketUrl = websocketUrl as string; + if (!socketUrl) { + // We will assume that it is always secure. + // This is because you can not have webRTC working with non-secure server. + // It works on non-secure server if it is tested on localhost. + if (parseInt(port as string) !== 443) { + socketUrl = `wss://${host}:${port}/${(path as string).replace('/', '')}`; + } else { + socketUrl = `wss://${host}/${(path as string).replace('/', '')}`; + } + } + + return API.v1.success(await Voip.checkCallserverConnection(socketUrl)); + }, + }, +); diff --git a/app/apple/client/index.ts b/app/apple/client/index.ts new file mode 100644 index 000000000000..3e4d15a67fe1 --- /dev/null +++ b/app/apple/client/index.ts @@ -0,0 +1,4 @@ +import { CustomOAuth } from '../../custom-oauth/client/custom_oauth_client'; +import { config } from '../lib/config'; + +new CustomOAuth('apple', config); diff --git a/app/apple/lib/config.ts b/app/apple/lib/config.ts new file mode 100644 index 000000000000..14f746b19a09 --- /dev/null +++ b/app/apple/lib/config.ts @@ -0,0 +1,10 @@ +export const config = { + serverURL: 'https://appleid.apple.com', + authorizePath: '/auth/authorize?response_mode=form_post', + responseType: 'code id_token', + tokenPath: '/auth/token', + scope: 'name email', + mergeUsers: true, + accessTokenParam: 'access_token', + loginStyle: 'popup', +}; diff --git a/app/apple/server/AppleCustomOAuth.ts b/app/apple/server/AppleCustomOAuth.ts new file mode 100644 index 000000000000..01c5027a44fb --- /dev/null +++ b/app/apple/server/AppleCustomOAuth.ts @@ -0,0 +1,72 @@ +import { Accounts } from 'meteor/accounts-base'; +import { HTTP } from 'meteor/http'; +import NodeRSA from 'node-rsa'; +import { KJUR } from 'jsrsasign'; + +import { CustomOAuth } from '../../custom-oauth/server/custom_oauth_server'; +import { MeteorError } from '../../../server/sdk/errors'; + +const isValidAppleJWT = (identityToken: string, header: any): any => { + const applePublicKeys = HTTP.get('https://appleid.apple.com/auth/keys').data.keys as any; + const { kid } = header; + + const key = applePublicKeys.find((k: any) => k.kid === kid); + + const pubKey = new NodeRSA(); + pubKey.importKey({ n: Buffer.from(key.n, 'base64'), e: Buffer.from(key.e, 'base64') }, 'components-public'); + const userKey = pubKey.exportKey('public'); + + try { + return KJUR.jws.JWS.verify(identityToken, userKey, ['RS256']); + } catch { + return false; + } +}; + +export class AppleCustomOAuth extends CustomOAuth { + getIdentity(_accessToken: string, query: Record): any { + const { id_token: identityToken, user: userStr = '' } = query; + + let user = {} as any; + try { + user = JSON.parse(userStr); + } catch (e) { + // ignore + } + + const decodedToken = KJUR.jws.JWS.parse(identityToken); + + if (!isValidAppleJWT(identityToken, decodedToken.headerObj)) { + return { + type: 'apple', + error: new MeteorError(Accounts.LoginCancelledError.numericError, 'identityToken is a invalid JWT'), + }; + } + + const { iss, sub, email } = decodedToken.payloadObj as any; + if (!iss) { + return { + type: 'apple', + error: new MeteorError(Accounts.LoginCancelledError.numericError, 'Insufficient data in auth response token'), + }; + } + + const serviceData = { + id: sub, + email, + name: '', + }; + + if (email) { + serviceData.email = email; + } + + if (user?.name) { + serviceData.name = `${user.name.firstName}${user.name.middleName ? ` ${user.name.middleName}` : ''}${ + user.name.lastName ? ` ${user.name.lastName}` : '' + }`; + } + + return serviceData; + } +} diff --git a/app/apple/server/appleOauthRegisterService.ts b/app/apple/server/appleOauthRegisterService.ts index 11753235a9b0..c7f01a680917 100644 --- a/app/apple/server/appleOauthRegisterService.ts +++ b/app/apple/server/appleOauthRegisterService.ts @@ -1,19 +1,11 @@ import { KJUR } from 'jsrsasign'; import { ServiceConfiguration } from 'meteor/service-configuration'; -import { CustomOAuth } from '../../custom-oauth/server/custom_oauth_server'; import { settings, settingsRegistry } from '../../settings/server'; +import { config } from '../lib/config'; +import { AppleCustomOAuth } from './AppleCustomOAuth'; -const config = { - serverURL: 'https://appleid.apple.com', - tokenPath: '/auth/token', - scope: 'name email', - mergeUsers: true, - accessTokenParam: 'access_token', - loginStyle: 'popup', -}; - -new CustomOAuth('apple', config); +export const AppleOAuth = new AppleCustomOAuth('apple', config); settingsRegistry.addGroup('OAuth', function () { this.section('Apple', function () { @@ -47,15 +39,22 @@ settings.watchMultiple( alg: 'ES256', }; - const tokenPayload = { - iss, - iat: Math.floor(Date.now() / 1000), - exp: Math.floor(Date.now() / 1000) + 300, - aud: 'https://appleid.apple.com', - sub: clientId, - }; + const now = new Date(); + const exp = new Date(); + exp.setMonth(exp.getMonth() + 5); // from Apple docs expiration time must no be greater than 6 months - const secret = KJUR.jws.JWS.sign(null, HEADER, tokenPayload, serverSecret as string); + const secret = KJUR.jws.JWS.sign( + null, + HEADER, + { + iss, + iat: Math.floor(now.getTime() / 1000), + exp: Math.floor(exp.getTime() / 1000), + aud: 'https://appleid.apple.com', + sub: clientId, + }, + serverSecret as string, + ); ServiceConfiguration.configurations.upsert( { @@ -63,12 +62,14 @@ settings.watchMultiple( }, { $set: { - // We'll hide this button on Web Client - showButton: false, + showButton: true, secret, enabled: settings.get('Accounts_OAuth_Apple'), loginStyle: 'popup', clientId, + buttonLabelText: 'Sign in with Apple', + buttonColor: '#000', + buttonLabelColor: '#FFF', }, }, ); diff --git a/app/apple/server/index.js b/app/apple/server/index.js deleted file mode 100644 index 7b8670be5e50..000000000000 --- a/app/apple/server/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import './startup'; -import './loginHandler.js'; diff --git a/app/apple/server/startup.ts b/app/apple/server/index.ts similarity index 100% rename from app/apple/server/startup.ts rename to app/apple/server/index.ts diff --git a/app/apple/server/loginHandler.js b/app/apple/server/loginHandler.js deleted file mode 100644 index 95bfee852c9c..000000000000 --- a/app/apple/server/loginHandler.js +++ /dev/null @@ -1,33 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { Accounts } from 'meteor/accounts-base'; - -import { handleIdentityToken } from './tokenHandler'; -import { settings } from '../../settings'; - -Accounts.registerLoginHandler('apple', (loginRequest) => { - if (!loginRequest.identityToken) { - return; - } - - if (!settings.get('Accounts_OAuth_Apple')) { - return; - } - - const identityResult = handleIdentityToken(loginRequest); - - if (!identityResult.error) { - const result = Accounts.updateOrCreateUserFromExternalService('apple', identityResult.serviceData, identityResult.options); - - // Ensure processing succeeded - if (result === undefined || result.userId === undefined) { - return { - type: 'apple', - error: new Meteor.Error(Accounts.LoginCancelledError.numericError, 'User creation failed from Apple response token'), - }; - } - - return result; - } - - return identityResult; -}); diff --git a/app/apple/server/tokenHandler.js b/app/apple/server/tokenHandler.js deleted file mode 100644 index c756e1985947..000000000000 --- a/app/apple/server/tokenHandler.js +++ /dev/null @@ -1,74 +0,0 @@ -import { jws } from 'jsrsasign'; -import NodeRSA from 'node-rsa'; -import { HTTP } from 'meteor/http'; -import { Meteor } from 'meteor/meteor'; -import { Accounts } from 'meteor/accounts-base'; -import { Match, check } from 'meteor/check'; - -const isValidAppleJWT = (identityToken, header) => { - const applePublicKeys = HTTP.get('https://appleid.apple.com/auth/keys').data.keys; - const { kid } = header; - - const key = applePublicKeys.find((k) => k.kid === kid); - - const pubKey = new NodeRSA(); - pubKey.importKey({ n: Buffer.from(key.n, 'base64'), e: Buffer.from(key.e, 'base64') }, 'components-public'); - const userKey = pubKey.exportKey(['public']); - - try { - return jws.JWS.verify(identityToken, userKey, { - typ: 'JWT', - alg: 'RS256', - }); - } catch { - return false; - } -}; - -export const handleIdentityToken = ({ identityToken, fullName = {}, email }) => { - check(identityToken, String); - check(fullName, Match.Maybe(Object)); - check(email, Match.Maybe(String)); - - const decodedToken = jws.JWS.parse(identityToken); - - if (!isValidAppleJWT(identityToken, decodedToken.headerObj)) { - return { - type: 'apple', - error: new Meteor.Error(Accounts.LoginCancelledError.numericError, 'identityToken is a invalid JWT'), - }; - } - - const profile = {}; - - const { givenName, familyName } = fullName; - if (givenName && familyName) { - profile.name = `${givenName} ${familyName}`; - } - - const { iss, iat, exp } = decodedToken.payloadObj; - - if (!iss) { - return { - type: 'apple', - error: new Meteor.Error(Accounts.LoginCancelledError.numericError, 'Insufficient data in auth response token'), - }; - } - - // Collect basic auth provider details - const serviceData = { - id: iss, - did: iss.split(':').pop(), - issuedAt: new Date(iat * 1000), - expiresAt: new Date(exp * 1000), - }; - - if (email) { - serviceData.email = email; - } - - return { - serviceData, - options: { profile }, - }; -}; diff --git a/app/apps/client/gameCenter/invitePlayers.js b/app/apps/client/gameCenter/invitePlayers.js index 9257bda6cfad..a6e0e1ac6110 100644 --- a/app/apps/client/gameCenter/invitePlayers.js +++ b/app/apps/client/gameCenter/invitePlayers.js @@ -8,7 +8,7 @@ import { Tracker } from 'meteor/tracker'; import { Session } from 'meteor/session'; import { AutoComplete } from '../../../meteor-autocomplete/client'; -import { roomTypes } from '../../../utils/client'; +import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator'; import { ChatRoom } from '../../../models/client'; import { modal } from '../../../ui-utils/client'; import { callWithErrorHandling } from '../../../../client/lib/utils/callWithErrorHandling'; @@ -84,7 +84,7 @@ Template.InvitePlayers.events({ try { const result = await callWithErrorHandling('createPrivateGroup', privateGroupName, users); - roomTypes.openRouteLink(result.t, result); + roomCoordinator.openRouteLink(result.t, result); // This ensures the message is only sent after the // user has been redirected to the new room, preventing a diff --git a/app/apps/client/orchestrator.js b/app/apps/client/orchestrator.js index 09244b03bfd2..a340fd2a3063 100644 --- a/app/apps/client/orchestrator.js +++ b/app/apps/client/orchestrator.js @@ -69,12 +69,13 @@ class AppClientOrchestrator { getAppsFromMarketplace = async () => { const appsOverviews = await APIClient.get('apps', { marketplace: 'true' }); - return appsOverviews.map(({ latest, price, pricingPlans, purchaseType, isEnterpriseOnly }) => ({ + return appsOverviews.map(({ latest, price, pricingPlans, purchaseType, isEnterpriseOnly, modifiedAt }) => ({ ...latest, price, pricingPlans, purchaseType, isEnterpriseOnly, + modifiedAt, })); }; diff --git a/app/apps/server/bridges/http.ts b/app/apps/server/bridges/http.ts index 46685ce9265b..e8c118af3c24 100644 --- a/app/apps/server/bridges/http.ts +++ b/app/apps/server/bridges/http.ts @@ -1,10 +1,9 @@ -import { fetch } from 'meteor/fetch'; import { HttpBridge } from '@rocket.chat/apps-engine/server/bridges/HttpBridge'; import { IHttpResponse } from '@rocket.chat/apps-engine/definition/accessors'; import { IHttpBridgeRequestInfo } from '@rocket.chat/apps-engine/server/bridges'; import { AppServerOrchestrator } from '../orchestrator'; -import { getUnsafeAgent } from '../../../../server/lib/getUnsafeAgent'; +import { fetch } from '../../../../server/lib/http/fetch'; const isGetOrHead = (method: string): boolean => ['GET', 'HEAD'].includes(method.toUpperCase()); @@ -70,15 +69,16 @@ export class AppHttpBridge extends HttpBridge { this.orch.debugLog(`The App ${info.appId} is requesting from the outter webs:`, info); try { - const response = await fetch(url.href, { - method, - body: content, - headers, - ...(((request.hasOwnProperty('strictSSL') && !request.strictSSL) || - (request.hasOwnProperty('rejectUnauthorized') && request.rejectUnauthorized)) && { - agent: getUnsafeAgent(url.protocol === 'https:' ? 'https:' : 'http:'), - }), - }); + const response = await fetch( + url.href, + { + method, + body: content, + headers, + }, + (request.hasOwnProperty('strictSSL') && !request.strictSSL) || + (request.hasOwnProperty('rejectUnauthorized') && request.rejectUnauthorized), + ); const result: IHttpResponse = { url: info.url, diff --git a/app/apps/server/bridges/livechat.ts b/app/apps/server/bridges/livechat.ts index 3c79c709fbb7..3c58c649d55e 100644 --- a/app/apps/server/bridges/livechat.ts +++ b/app/apps/server/bridges/livechat.ts @@ -111,7 +111,7 @@ export class AppLivechatBridge extends LivechatBridge { protected async closeRoom(room: ILivechatRoom, comment: string, closer: IUser | undefined, appId: string): Promise { this.orch.debugLog(`The App ${appId} is closing a livechat room.`); - const user = closer && this.orch.getConverters()?.get('users').convertById(closer.id); + const user = closer && this.orch.getConverters()?.get('users').convertToRocketChat(closer); const visitor = this.orch.getConverters()?.get('visitors').convertAppVisitor(room.visitor); const closeData: any = { diff --git a/app/apps/server/bridges/uiInteraction.ts b/app/apps/server/bridges/uiInteraction.ts index 56c1fa174e08..56a3b48ccc30 100644 --- a/app/apps/server/bridges/uiInteraction.ts +++ b/app/apps/server/bridges/uiInteraction.ts @@ -2,7 +2,7 @@ import { UiInteractionBridge as UiIntBridge } from '@rocket.chat/apps-engine/ser import { IUIKitInteraction } from '@rocket.chat/apps-engine/definition/uikit'; import { IUser } from '@rocket.chat/apps-engine/definition/users'; -import { Notifications } from '../../../notifications/server'; +import { api } from '../../../../server/sdk/api'; import { AppServerOrchestrator } from '../orchestrator'; export class UiInteractionBridge extends UiIntBridge { @@ -20,6 +20,6 @@ export class UiInteractionBridge extends UiIntBridge { throw new Error('Invalid app provided'); } - Notifications.notifyUser(user.id, 'uiInteraction', interaction); + api.broadcast('notify.uiInteraction', user.id, interaction); } } diff --git a/app/apps/server/communication/rest.js b/app/apps/server/communication/rest.js index e169d9a1ce81..b15bca56772a 100644 --- a/app/apps/server/communication/rest.js +++ b/app/apps/server/communication/rest.js @@ -1,6 +1,5 @@ import { Meteor } from 'meteor/meteor'; import { HTTP } from 'meteor/http'; -import { fetch } from 'meteor/fetch'; import { API } from '../../../api/server'; import { getUploadFormData } from '../../../api/server/lib/getUploadFormData'; @@ -12,6 +11,7 @@ import { Settings } from '../../../models/server/raw'; import { Apps } from '../orchestrator'; import { formatAppInstanceForRest } from '../../lib/misc/formatAppInstanceForRest'; import { actionButtonsHandler } from './endpoints/actionButtonsHandler'; +import { fetch } from '../../../../server/lib/http/fetch'; import { updateAppSettingById, updateAgentConfig, addAgentConfig, deleteAgentConfig, getAgentConfig } from './appConfig'; const appsEngineVersionForMarketplace = Info.marketplaceApiVersion.replace(/-.*/g, ''); diff --git a/app/apps/server/converters/rooms.js b/app/apps/server/converters/rooms.js index ecd2385557e0..12857a94e117 100644 --- a/app/apps/server/converters/rooms.js +++ b/app/apps/server/converters/rooms.js @@ -41,6 +41,7 @@ export class AppRoomsConverter { _id: visitor._id, username: visitor.username, token: visitor.token, + status: visitor.status || 'online', }; } diff --git a/app/apps/server/converters/visitors.js b/app/apps/server/converters/visitors.js index 1f698cf41ee4..40c29e1c59a8 100644 --- a/app/apps/server/converters/visitors.js +++ b/app/apps/server/converters/visitors.js @@ -33,6 +33,7 @@ export class AppVisitorsConverter { phone: 'phone', visitorEmails: 'visitorEmails', livechatData: 'livechatData', + status: 'status', }; return transformMappedData(visitor, map); @@ -50,6 +51,7 @@ export class AppVisitorsConverter { token: visitor.token, phone: visitor.phone, livechatData: visitor.livechatData, + status: visitor.status || 'online', ...(visitor.visitorEmails && { visitorEmails: visitor.visitorEmails }), ...(visitor.department && { department: visitor.department }), }; diff --git a/app/authentication/server/startup/index.js b/app/authentication/server/startup/index.js index eb0ed5415673..557c6454fee0 100644 --- a/app/authentication/server/startup/index.js +++ b/app/authentication/server/startup/index.js @@ -8,10 +8,11 @@ import { escapeRegExp, escapeHTML } from '@rocket.chat/string-helpers'; import * as Mailer from '../../../mailer/server/api'; import { settings } from '../../../settings/server'; import { callbacks } from '../../../../lib/callbacks'; -import { Users, Settings } from '../../../models/server'; +import { Settings, Users } from '../../../models/server'; import { Roles, Users as UsersRaw } from '../../../models/server/raw'; -import { addUserRoles } from '../../../authorization/server'; +import { addUserRoles } from '../../../../server/lib/roles/addUserRoles'; import { getAvatarSuggestionForUser } from '../../../lib/server/functions/getAvatarSuggestionForUser'; +import { parseCSV } from '../../../../lib/utils/parseCSV'; import { isValidAttemptByUser, isValidLoginAttemptByIp } from '../lib/restrictLoginAttempts'; import './settings'; import { getClientAddress } from '../../../../server/lib/getClientAddress'; @@ -213,8 +214,6 @@ Accounts.onCreateUser(function (options, user = {}) { }); Accounts.insertUserDoc = _.wrap(Accounts.insertUserDoc, function (insertUserDoc, options, user) { - const noRoles = !user?.hasOwnProperty('globalRoles'); - const globalRoles = []; if (Match.test(user.globalRoles, [String]) && user.globalRoles.length > 0) { @@ -224,9 +223,10 @@ Accounts.insertUserDoc = _.wrap(Accounts.insertUserDoc, function (insertUserDoc, delete user.globalRoles; if (user.services && !user.services.password) { - const defaultAuthServiceRoles = String(settings.get('Accounts_Registration_AuthenticationServices_Default_Roles')).split(','); + const defaultAuthServiceRoles = parseCSV(settings.get('Accounts_Registration_AuthenticationServices_Default_Roles') || ''); + if (defaultAuthServiceRoles.length > 0) { - globalRoles.push(...defaultAuthServiceRoles.map((s) => s.trim())); + globalRoles.push(...defaultAuthServiceRoles); } } @@ -278,26 +278,18 @@ Accounts.insertUserDoc = _.wrap(Accounts.insertUserDoc, function (insertUserDoc, } } - if (noRoles || roles.length === 0) { - const hasAdmin = Users.findOne( - { - roles: 'admin', - type: 'user', - }, - { - fields: { - _id: 1, - }, - }, - ); - - if (hasAdmin) { - roles.push('user'); - } else { - roles.push('admin'); - if (settings.get('Show_Setup_Wizard') === 'pending') { - Settings.updateValueById('Show_Setup_Wizard', 'in_progress'); - } + /** + * if settings shows setup wizard to be pending + * and no admin's been found, + * and existing role list doesn't include admin + * create this user admin. + * count this as the completion of setup wizard step 1. + */ + const hasAdmin = Users.findOneByRolesAndType('admin', 'user', { fields: { _id: 1 } }); + if (!roles.includes('admin') && !hasAdmin) { + roles.push('admin'); + if (settings.get('Show_Setup_Wizard') === 'pending') { + Settings.updateValueById('Show_Setup_Wizard', 'in_progress'); } } diff --git a/app/authorization/README.md b/app/authorization/README.md index c2365d83f698..93d86ed2092b 100644 --- a/app/authorization/README.md +++ b/app/authorization/README.md @@ -11,7 +11,8 @@ if hasPermission(userId, 'edit-message') ... # at runtime by removing the permission for user's role instead of modifying the action code. # role based check -if hasRole(userId, ['admin','site-moderator','moderator']) +if hasRole(userId, 'admin') +if hasAnyRole(userId, ['admin','site-moderator','moderator']) # action is statically associated with the role # action code has to be modified to add/remove role authorization @@ -20,11 +21,11 @@ if hasRole(userId, ['admin','site-moderator','moderator']) Usage: ``` # assign user to admin role. Permissions scoped globally -RocketChat.authz.addUserRoles(userId, 'admin') +RocketChat.authz.addUserRoles(userId, ['admin']) # assign user to moderator role. Permissions scoped to the specified room # user can moderate (e.g. edit channel name, delete private group message) for only one room specified by the roomId -RocketChat.authz.addUserRoles(userId, 'moderator', roomId ) +RocketChat.authz.addUserRoles(userId, ['moderator'], roomId ) # check if user can modify message for any room RocketChat.authz.hasPermission(userId, 'edit-message') diff --git a/app/authorization/client/hasPermission.ts b/app/authorization/client/hasPermission.ts index ce09644d62bb..144efe9c7dce 100644 --- a/app/authorization/client/hasPermission.ts +++ b/app/authorization/client/hasPermission.ts @@ -28,8 +28,8 @@ const createPermissionValidator = }); const roles = permission?.roles ?? []; - return roles.some((roleName) => { - const role = Models.Roles.findOne(roleName, { fields: { scope: 1 } }); + return roles.some((roleId) => { + const role = Models.Roles.findOne(roleId, { fields: { scope: 1 } }); const roleScope = role?.scope; if (!isValidScope(roleScope)) { @@ -37,7 +37,7 @@ const createPermissionValidator = } const model = Models[roleScope as keyof typeof Models]; - return model.isUserInRole && model.isUserInRole(userId, roleName, scope); + return model.isUserInRole && model.isUserInRole(userId, roleId, scope); }); }); }; diff --git a/app/authorization/client/hasRole.js b/app/authorization/client/hasRole.js deleted file mode 100644 index 710ae1803cae..000000000000 --- a/app/authorization/client/hasRole.js +++ /dev/null @@ -1,6 +0,0 @@ -import { Roles } from '../../models'; - -export const hasRole = (userId, roleNames, scope) => { - roleNames = [].concat(roleNames); - return Roles.isUserInRoles(userId, roleNames, scope); -}; diff --git a/app/authorization/client/hasRole.ts b/app/authorization/client/hasRole.ts new file mode 100644 index 000000000000..b96350e1b73f --- /dev/null +++ b/app/authorization/client/hasRole.ts @@ -0,0 +1,19 @@ +import { Roles } from '../../models/client'; +import type { IUser, IRole } from '../../../definition/IUser'; +import type { IRoom } from '../../../definition/IRoom'; + +export const hasRole = (userId: IUser['_id'], roleId: IRole['_id'], scope?: IRoom['_id']): boolean => { + if (Array.isArray(roleId)) { + throw new Error('error-invalid-arguments'); + } + + return Roles.isUserInRoles(userId, [roleId], scope); +}; + +export const hasAnyRole = (userId: IUser['_id'], roleIds: IRole['_id'][], scope?: IRoom['_id']): boolean => { + if (!Array.isArray(roleIds)) { + throw new Error('error-invalid-arguments'); + } + + return Roles.isUserInRoles(userId, roleIds, scope); +}; diff --git a/app/authorization/client/index.js b/app/authorization/client/index.js index 390b615ec481..ea320d1601f2 100644 --- a/app/authorization/client/index.js +++ b/app/authorization/client/index.js @@ -1,7 +1,7 @@ import { hasAllPermission, hasAtLeastOnePermission, hasPermission, userHasAllPermission } from './hasPermission'; -import { hasRole } from './hasRole'; +import { hasRole, hasAnyRole } from './hasRole'; import { AuthorizationUtils } from '../lib/AuthorizationUtils'; import './requiresPermission.html'; import './startup'; -export { hasAllPermission, hasAtLeastOnePermission, hasRole, hasPermission, userHasAllPermission, AuthorizationUtils }; +export { hasAllPermission, hasAtLeastOnePermission, hasRole, hasAnyRole, hasPermission, userHasAllPermission, AuthorizationUtils }; diff --git a/app/authorization/client/startup.js b/app/authorization/client/startup.js index 9fad47450a8a..8bdc2c958e27 100644 --- a/app/authorization/client/startup.js +++ b/app/authorization/client/startup.js @@ -12,7 +12,7 @@ Meteor.startup(() => { CachedCollectionManager.onLogin(async () => { const { roles } = await APIClient.v1.get('roles.list'); // if a role is checked before this collection is populated, it will return undefined - Roles._collection._docs._map = new Map(roles.map((record) => [record._id, record])); + Roles._collection._docs._map = new Map(roles.map((record) => [Roles._collection._docs._idStringify(record._id), record])); Object.values(Roles._collection.queries).forEach((query) => Roles._collection._recomputeResults(query)); Roles.ready.set(true); diff --git a/app/authorization/server/functions/addUserRoles.ts b/app/authorization/server/functions/addUserRoles.ts deleted file mode 100644 index b5b7b7913f13..000000000000 --- a/app/authorization/server/functions/addUserRoles.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import _ from 'underscore'; - -import { getRoles } from './getRoles'; -import { Users } from '../../../models/server'; -import { IRole, IUser } from '../../../../definition/IUser'; -import { Roles } from '../../../models/server/raw'; - -export const addUserRoles = (userId: IUser['_id'], roleNames: IRole['name'][], scope?: string): boolean => { - if (!userId || !roleNames) { - return false; - } - - const user = Users.db.findOneById(userId); - if (!user) { - throw new Meteor.Error('error-invalid-user', 'Invalid user', { - function: 'RocketChat.authz.addUserRoles', - }); - } - - if (!Array.isArray(roleNames)) { - // TODO: remove this check - roleNames = [roleNames]; - } - - const existingRoleNames = _.pluck(getRoles(), '_id'); - const invalidRoleNames = _.difference(roleNames, existingRoleNames); - - if (!_.isEmpty(invalidRoleNames)) { - for (const role of invalidRoleNames) { - Promise.await(Roles.createOrUpdate(role)); - } - } - - Promise.await(Roles.addUserRoles(userId, roleNames, scope)); - return true; -}; diff --git a/app/authorization/server/functions/canAccessRoom.ts b/app/authorization/server/functions/canAccessRoom.ts index d232f890af2e..7b0361f7719d 100644 --- a/app/authorization/server/functions/canAccessRoom.ts +++ b/app/authorization/server/functions/canAccessRoom.ts @@ -2,5 +2,15 @@ import { Authorization } from '../../../../server/sdk'; import { IAuthorization } from '../../../../server/sdk/types/IAuthorization'; export const canAccessRoomAsync = Authorization.canAccessRoom; +export const canAccessRoomIdAsync = Authorization.canAccessRoomId; +export const roomAccessAttributes = { + _id: 1, + t: 1, + teamId: 1, + prid: 1, + tokenpass: 1, +}; export const canAccessRoom = (...args: Parameters): boolean => Promise.await(canAccessRoomAsync(...args)); +export const canAccessRoomId = (...args: Parameters): boolean => + Promise.await(canAccessRoomIdAsync(...args)); diff --git a/app/authorization/server/functions/canDeleteMessage.js b/app/authorization/server/functions/canDeleteMessage.js index 0463acd0dad0..1e5f2a577eef 100644 --- a/app/authorization/server/functions/canDeleteMessage.js +++ b/app/authorization/server/functions/canDeleteMessage.js @@ -25,7 +25,7 @@ export const canDeleteMessageAsync = async (uid, { u, rid, ts }) => { const allowedToDeleteAny = await hasPermissionAsync(uid, 'delete-message', rid); - const allowed = allowedToDeleteAny || (uid === u._id && (await hasPermissionAsync(uid, 'delete-own-message'))); + const allowed = allowedToDeleteAny || (uid === u._id && (await hasPermissionAsync(uid, 'delete-own-message', rid))); if (!allowed) { return false; } diff --git a/app/authorization/server/functions/canSendMessage.js b/app/authorization/server/functions/canSendMessage.js index c346d6791a07..4b052a01ff46 100644 --- a/app/authorization/server/functions/canSendMessage.js +++ b/app/authorization/server/functions/canSendMessage.js @@ -1,7 +1,8 @@ import { canAccessRoomAsync } from './canAccessRoom'; import { hasPermissionAsync } from './hasPermission'; import { Subscriptions, Rooms } from '../../../models/server/raw'; -import { roomTypes, RoomMemberActions } from '../../../utils/server'; +import { RoomMemberActions } from '../../../../definition/IRoomTypeConfig'; +import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; const subscriptionOptions = { projection: { @@ -19,7 +20,7 @@ export const validateRoomMessagePermissionsAsync = async (room, { uid, username, throw new Error('error-not-allowed'); } - if (roomTypes.getConfig(room.t).allowMemberAction(room, RoomMemberActions.BLOCK)) { + if (roomCoordinator.getRoomDirectives(room.t)?.allowMemberAction(room, RoomMemberActions.BLOCK)) { const subscription = await Subscriptions.findOneByRoomIdAndUserId(room._id, uid, subscriptionOptions); if (subscription && (subscription.blocked || subscription.blocker)) { throw new Error('room_is_blocked'); diff --git a/app/authorization/server/functions/getUsersInRole.ts b/app/authorization/server/functions/getUsersInRole.ts index 17b5fcd86474..f7ca59260149 100644 --- a/app/authorization/server/functions/getUsersInRole.ts +++ b/app/authorization/server/functions/getUsersInRole.ts @@ -3,24 +3,24 @@ import { Cursor, FindOneOptions, WithoutProjection } from 'mongodb'; import { IRole, IUser } from '../../../../definition/IUser'; import { Roles } from '../../../models/server/raw'; -export function getUsersInRole(name: IRole['name'], scope?: string): Promise>; +export function getUsersInRole(roleId: IRole['_id'], scope?: string): Promise>; export function getUsersInRole( - name: IRole['name'], + roleId: IRole['_id'], scope: string | undefined, options: WithoutProjection>, ): Promise>; export function getUsersInRole

( - name: IRole['name'], + roleId: IRole['_id'], scope: string | undefined, options: FindOneOptions

, ): Promise>; export function getUsersInRole

( - name: IRole['name'], + roleId: IRole['_id'], scope: string | undefined, options?: any | undefined, ): Promise> { - return Roles.findUsersInRole(name, scope, options); + return Roles.findUsersInRole(roleId, scope, options); } diff --git a/app/authorization/server/functions/hasPermission.js b/app/authorization/server/functions/hasPermission.js deleted file mode 100644 index b907fc999bee..000000000000 --- a/app/authorization/server/functions/hasPermission.js +++ /dev/null @@ -1,11 +0,0 @@ -import { Authorization } from '../../../../server/sdk'; - -export const hasAllPermissionAsync = async (userId, permissions, scope) => Authorization.hasAllPermission(userId, permissions, scope); -export const hasPermissionAsync = async (userId, permissionId, scope) => Authorization.hasPermission(userId, permissionId, scope); -export const hasAtLeastOnePermissionAsync = async (userId, permissions, scope) => - Authorization.hasAtLeastOnePermission(userId, permissions, scope); - -export const hasAllPermission = (userId, permissions, scope) => Promise.await(hasAllPermissionAsync(userId, permissions, scope)); -export const hasPermission = (userId, permissionId, scope) => Promise.await(hasPermissionAsync(userId, permissionId, scope)); -export const hasAtLeastOnePermission = (userId, permissions, scope) => - Promise.await(hasAtLeastOnePermissionAsync(userId, permissions, scope)); diff --git a/app/authorization/server/functions/hasPermission.ts b/app/authorization/server/functions/hasPermission.ts new file mode 100644 index 000000000000..b87633285fe3 --- /dev/null +++ b/app/authorization/server/functions/hasPermission.ts @@ -0,0 +1,23 @@ +import { Authorization } from '../../../../server/sdk'; +import type { IUser } from '../../../../definition/IUser'; +import type { IPermission } from '../../../../definition/IPermission'; +import type { IRoom } from '../../../../definition/IRoom'; + +export const hasAllPermissionAsync = async ( + userId: IUser['_id'], + permissions: IPermission['_id'][], + scope?: IRoom['_id'], +): Promise => Authorization.hasAllPermission(userId, permissions, scope); +export const hasPermissionAsync = async (userId: IUser['_id'], permissionId: IPermission['_id'], scope?: IRoom['_id']): Promise => + Authorization.hasPermission(userId, permissionId, scope); +export const hasAtLeastOnePermissionAsync = async ( + userId: IUser['_id'], + permissions: IPermission['_id'][], + scope?: IRoom['_id'], +): Promise => Authorization.hasAtLeastOnePermission(userId, permissions, scope); + +export const hasAllPermission = (...args: Parameters): boolean => + Promise.await(hasAllPermissionAsync(...args)); +export const hasPermission = (...args: Parameters): boolean => Promise.await(hasPermissionAsync(...args)); +export const hasAtLeastOnePermission = (...args: Parameters): boolean => + Promise.await(hasAtLeastOnePermissionAsync(...args)); diff --git a/app/authorization/server/functions/hasRole.ts b/app/authorization/server/functions/hasRole.ts index f26ff3208c58..518c851c46ea 100644 --- a/app/authorization/server/functions/hasRole.ts +++ b/app/authorization/server/functions/hasRole.ts @@ -1,18 +1,34 @@ +import type { IRole, IUser } from '../../../../definition/IUser'; +import type { IRoom } from '../../../../definition/IRoom'; import { Roles } from '../../../models/server/raw'; import { ISubscription } from '../../../../definition/ISubscription'; -export const hasAnyRoleAsync = async (userId: string, roleNames: string[], scope?: string | undefined): Promise => { +export const hasAnyRoleAsync = async ( + userId: IUser['_id'], + roleIds: IRole['_id'][], + scope?: IRoom['_id'] | undefined, +): Promise => { + if (!Array.isArray(roleIds)) { + throw new Error('error-invalid-arguments'); + } + if (!userId || userId === '') { return false; } - return Roles.isUserInRoles(userId, roleNames, scope); + return Roles.isUserInRoles(userId, roleIds, scope); +}; + +export const hasRoleAsync = async (userId: IUser['_id'], roleId: IRole['_id'], scope?: IRoom['_id'] | undefined): Promise => { + if (Array.isArray(roleId)) { + throw new Error('error-invalid-arguments'); + } + + return hasAnyRoleAsync(userId, [roleId], scope); }; -export const hasRole = (userId: string, roleNames: string, scope?: string | undefined): boolean => - Promise.await(hasAnyRoleAsync(userId, [roleNames], scope)); +export const hasRole = (...args: Parameters): boolean => Promise.await(hasRoleAsync(...args)); -export const hasAnyRole = (userId: string, roleNames: string[], scope?: string | undefined): boolean => - Promise.await(hasAnyRoleAsync(userId, roleNames, scope)); +export const hasAnyRole = (...args: Parameters): boolean => Promise.await(hasAnyRoleAsync(...args)); -export const subscriptionHasRole = (sub: ISubscription, role: string): boolean | undefined => sub.roles && sub.roles.includes(role); +export const subscriptionHasRole = (sub: ISubscription, role: IRole['_id']): boolean | undefined => sub.roles && sub.roles.includes(role); diff --git a/app/authorization/server/functions/removeUserFromRoles.js b/app/authorization/server/functions/removeUserFromRoles.js deleted file mode 100644 index a55d722bb891..000000000000 --- a/app/authorization/server/functions/removeUserFromRoles.js +++ /dev/null @@ -1,34 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import _ from 'underscore'; - -import { getRoles } from './getRoles'; -import { Users } from '../../../models/server'; -import { Roles } from '../../../models/server/raw'; - -export const removeUserFromRoles = (userId, roleNames, scope) => { - if (!userId || !roleNames) { - return false; - } - - const user = Users.findOneById(userId); - - if (!user) { - throw new Meteor.Error('error-invalid-user', 'Invalid user', { - function: 'RocketChat.authz.removeUserFromRoles', - }); - } - - roleNames = [].concat(roleNames); - const existingRoleNames = _.pluck(getRoles(), '_id'); - const invalidRoleNames = _.difference(roleNames, existingRoleNames); - - if (!_.isEmpty(invalidRoleNames)) { - throw new Meteor.Error('error-invalid-role', 'Invalid role', { - function: 'RocketChat.authz.removeUserFromRoles', - }); - } - - Promise.await(Roles.removeUserRoles(userId, roleNames, scope)); - - return true; -}; diff --git a/app/authorization/server/functions/upsertPermissions.ts b/app/authorization/server/functions/upsertPermissions.ts index abfa562122be..ccf2f827bf8d 100644 --- a/app/authorization/server/functions/upsertPermissions.ts +++ b/app/authorization/server/functions/upsertPermissions.ts @@ -1,9 +1,10 @@ /* eslint no-multi-spaces: 0 */ import { settings } from '../../../settings/server'; import { getSettingPermissionId, CONSTANTS } from '../../lib'; -import { Permissions, Roles, Settings } from '../../../models/server/raw'; +import { Permissions, Settings } from '../../../models/server/raw'; import { IPermission } from '../../../../definition/IPermission'; import { ISetting } from '../../../../definition/ISetting'; +import { createOrUpdateProtectedRoleAsync } from '../../../../server/lib/roles/createOrUpdateProtectedRole'; export const upsertPermissions = async (): Promise => { // Note: @@ -174,7 +175,7 @@ export const upsertPermissions = async (): Promise => { }, { _id: 'send-omnichannel-chat-transcript', roles: ['livechat-manager', 'admin'] }, { _id: 'mail-messages', roles: ['admin'] }, - { _id: 'toggle-room-e2e-encryption', roles: ['owner'] }, + { _id: 'toggle-room-e2e-encryption', roles: ['owner', 'admin'] }, { _id: 'message-impersonate', roles: ['bot', 'app'] }, { _id: 'create-team', roles: ['admin', 'user'] }, { _id: 'delete-team', roles: ['admin', 'owner'] }, @@ -188,6 +189,18 @@ export const upsertPermissions = async (): Promise => { { _id: 'view-all-team-channels', roles: ['admin', 'owner'] }, { _id: 'view-all-teams', roles: ['admin'] }, { _id: 'remove-closed-livechat-room', roles: ['livechat-manager', 'admin'] }, + { _id: 'remove-livechat-department', roles: ['livechat-manager', 'admin'] }, + + // VOIP Permissions + // allows to manage voip calls configuration + { _id: 'manage-voip-call-settings', roles: ['livechat-manager', 'admin'] }, + { _id: 'manage-voip-contact-center-settings', roles: ['livechat-manager', 'admin'] }, + // allows agent-extension association. + { _id: 'manage-agent-extension-association', roles: ['admin'] }, + { _id: 'view-agent-extension-association', roles: ['livechat-manager', 'admin', 'livechat-agent'] }, + // allows to receive a voip call + { _id: 'inbound-voip-calls', roles: ['livechat-agent'] }, + { _id: 'remove-livechat-department', roles: ['livechat-manager', 'admin'] }, { _id: 'manage-apps', roles: ['admin'] }, { _id: 'post-readonly', roles: ['admin', 'owner', 'moderator'] }, @@ -199,6 +212,18 @@ export const upsertPermissions = async (): Promise => { { _id: 'pin-message', roles: ['owner', 'moderator', 'admin'] }, { _id: 'snippet-message', roles: ['owner', 'moderator', 'admin'] }, { _id: 'mobile-upload-file', roles: ['user', 'admin'] }, + { _id: 'send-mail', roles: ['admin'] }, + { _id: 'view-federation-data', roles: ['admin'] }, + { _id: 'add-all-to-room', roles: ['admin'] }, + { _id: 'get-server-info', roles: ['admin'] }, + { _id: 'register-on-cloud', roles: ['admin'] }, + { _id: 'test-admin-options', roles: ['admin'] }, + { _id: 'sync-auth-services-users', roles: ['admin'] }, + { _id: 'manage-chatpal', roles: ['admin'] }, + { _id: 'restart-server', roles: ['admin'] }, + { _id: 'remove-slackbridge-links', roles: ['admin'] }, + { _id: 'view-import-operations', roles: ['admin'] }, + { _id: 'clear-oembed-cache', roles: ['admin'] }, ]; for await (const permission of permissions) { @@ -217,10 +242,10 @@ export const upsertPermissions = async (): Promise => { { name: 'anonymous', scope: 'Users', description: '' }, { name: 'livechat-agent', scope: 'Users', description: 'Livechat Agent' }, { name: 'livechat-manager', scope: 'Users', description: 'Livechat Manager' }, - ]; + ] as const; for await (const role of defaultRoles) { - await Roles.createOrUpdate(role.name, role.scope as 'Users' | 'Subscriptions', role.description, true, false); + await createOrUpdateProtectedRoleAsync(role.name, role); } const getPreviousPermissions = async function (settingId?: string): Promise> { @@ -275,7 +300,7 @@ export const upsertPermissions = async (): Promise => { try { await Permissions.update({ _id: permissionId }, { $set: permission }, { upsert: true }); } catch (e) { - if (!e.message.includes('E11000')) { + if (!(e as Error).message.includes('E11000')) { // E11000 refers to a MongoDB error that can occur when using unique indexes for upserts // https://docs.mongodb.com/manual/reference/method/db.collection.update/#use-unique-indexes await Permissions.update({ _id: permissionId }, { $set: permission }, { upsert: true }); diff --git a/app/authorization/server/index.js b/app/authorization/server/index.js index 6013fd9c4d63..b43cbeabc51d 100644 --- a/app/authorization/server/index.js +++ b/app/authorization/server/index.js @@ -1,11 +1,9 @@ -import { addUserRoles } from './functions/addUserRoles'; -import { canAccessRoom, roomAccessValidators } from './functions/canAccessRoom'; +import { canAccessRoom, canAccessRoomId, roomAccessAttributes, roomAccessValidators } from './functions/canAccessRoom'; import { canSendMessage, validateRoomMessagePermissions } from './functions/canSendMessage'; import { getRoles } from './functions/getRoles'; import { getUsersInRole } from './functions/getUsersInRole'; import { hasAllPermission, hasAtLeastOnePermission, hasPermission } from './functions/hasPermission'; -import { hasRole, subscriptionHasRole } from './functions/hasRole'; -import { removeUserFromRoles } from './functions/removeUserFromRoles'; +import { hasRole, hasAnyRole, subscriptionHasRole } from './functions/hasRole'; import { AuthorizationUtils } from '../lib/AuthorizationUtils'; import './methods/addPermissionToRole'; import './methods/addUserToRole'; @@ -19,13 +17,14 @@ export { getRoles, getUsersInRole, hasRole, + hasAnyRole, subscriptionHasRole, - removeUserFromRoles, canSendMessage, validateRoomMessagePermissions, roomAccessValidators, - addUserRoles, canAccessRoom, + canAccessRoomId, + roomAccessAttributes, hasAllPermission, hasAtLeastOnePermission, hasPermission, diff --git a/app/authorization/server/methods/addUserToRole.ts b/app/authorization/server/methods/addUserToRole.ts index cfd4919000e0..1d67ee41aef6 100644 --- a/app/authorization/server/methods/addUserToRole.ts +++ b/app/authorization/server/methods/addUserToRole.ts @@ -6,23 +6,42 @@ import { settings } from '../../../settings/server'; import { hasPermission } from '../functions/hasPermission'; import { api } from '../../../../server/sdk/api'; import { Roles } from '../../../models/server/raw'; +import type { IRole } from '../../../../definition/IRole'; +import type { IUser } from '../../../../definition/IUser'; +import type { IRoom } from '../../../../definition/IRoom'; +import { apiDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; Meteor.methods({ - async 'authorization:addUserToRole'(roleName, username, scope) { - if (!Meteor.userId() || !hasPermission(Meteor.userId(), 'access-permissions')) { + async 'authorization:addUserToRole'(roleId: IRole['_id'], username: IUser['username'], scope: IRoom['_id'] | undefined) { + const userId = Meteor.userId(); + + if (!userId || !hasPermission(userId, 'access-permissions')) { throw new Meteor.Error('error-action-not-allowed', 'Accessing permissions is not allowed', { method: 'authorization:addUserToRole', action: 'Accessing_permissions', }); } - if (!roleName || !_.isString(roleName) || !username || !_.isString(username)) { + if (!roleId || !_.isString(roleId) || !username || !_.isString(username)) { throw new Meteor.Error('error-invalid-arguments', 'Invalid arguments', { method: 'authorization:addUserToRole', }); } - if (roleName === 'admin' && !hasPermission(Meteor.userId(), 'assign-admin-role')) { + let role = await Roles.findOneById>(roleId, { projection: { _id: 1 } }); + if (!role) { + role = await Roles.findOneByName>(roleId, { projection: { _id: 1 } }); + + if (!role) { + throw new Meteor.Error('error-invalid-role', 'Invalid Role', { + method: 'authorization:addUserToRole', + }); + } + + apiDeprecationLogger.warn(`Calling authorization:addUserToRole with role names will be deprecated in future versions of Rocket.Chat`); + } + + if (role._id === 'admin' && !hasPermission(userId, 'assign-admin-role')) { throw new Meteor.Error('error-action-not-allowed', 'Assigning admin is not allowed', { method: 'authorization:addUserToRole', action: 'Assign_admin', @@ -42,18 +61,18 @@ Meteor.methods({ } // verify if user can be added to given scope - if (scope && !(await Roles.canAddUserToRole(user._id, roleName, scope))) { + if (scope && !(await Roles.canAddUserToRole(user._id, role._id, scope))) { throw new Meteor.Error('error-invalid-user', 'User is not part of given room', { method: 'authorization:addUserToRole', }); } - const add = await Roles.addUserRoles(user._id, [roleName], scope); + const add = await Roles.addUserRoles(user._id, [role._id], scope); if (settings.get('UI_DisplayRoles')) { api.broadcast('user.roleUpdate', { type: 'added', - _id: roleName, + _id: role._id, u: { _id: user._id, username, diff --git a/app/authorization/server/methods/deleteRole.ts b/app/authorization/server/methods/deleteRole.ts index 78d0b0f0c6e0..da975c4f940c 100644 --- a/app/authorization/server/methods/deleteRole.ts +++ b/app/authorization/server/methods/deleteRole.ts @@ -2,21 +2,38 @@ import { Meteor } from 'meteor/meteor'; import { Roles } from '../../../models/server/raw'; import { hasPermission } from '../functions/hasPermission'; +import type { IRole } from '../../../../definition/IRole'; +import { apiDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; Meteor.methods({ - async 'authorization:deleteRole'(roleName) { - if (!Meteor.userId() || !hasPermission(Meteor.userId(), 'access-permissions')) { + async 'authorization:deleteRole'(roleId) { + const userId = Meteor.userId(); + + if (!userId || !hasPermission(userId, 'access-permissions')) { throw new Meteor.Error('error-action-not-allowed', 'Accessing permissions is not allowed', { method: 'authorization:deleteRole', action: 'Accessing_permissions', }); } - const role = await Roles.findOne(roleName); + const options = { + projection: { + _id: 1, + protected: 1, + }, + }; + + let role = await Roles.findOneById>(roleId, options); if (!role) { - throw new Meteor.Error('error-invalid-role', 'Invalid role', { - method: 'authorization:deleteRole', - }); + role = await Roles.findOneByName>(roleId, options); + + if (!role) { + throw new Meteor.Error('error-invalid-role', 'Invalid role', { + method: 'authorization:deleteRole', + }); + } + + apiDeprecationLogger.warn(`Calling authorization:deleteRole with role names will be deprecated in future versions of Rocket.Chat`); } if (role.protected) { @@ -25,7 +42,7 @@ Meteor.methods({ }); } - const users = await (await Roles.findUsersInRole(roleName)).count(); + const users = await (await Roles.findUsersInRole(role._id)).count(); if (users > 0) { throw new Meteor.Error('error-role-in-use', "Cannot delete role because it's in use", { @@ -33,6 +50,6 @@ Meteor.methods({ }); } - return Roles.removeById(role.name); + return Roles.removeById(role._id); }, }); diff --git a/app/authorization/server/methods/removeUserFromRole.js b/app/authorization/server/methods/removeUserFromRole.ts similarity index 54% rename from app/authorization/server/methods/removeUserFromRole.js rename to app/authorization/server/methods/removeUserFromRole.ts index bb06d0e76226..ed2b881b41e8 100644 --- a/app/authorization/server/methods/removeUserFromRole.js +++ b/app/authorization/server/methods/removeUserFromRole.ts @@ -1,37 +1,51 @@ import { Meteor } from 'meteor/meteor'; import _ from 'underscore'; +import { Users } from '../../../models/server'; import { settings } from '../../../settings/server'; import { hasPermission } from '../functions/hasPermission'; import { api } from '../../../../server/sdk/api'; import { Roles } from '../../../models/server/raw'; +import type { IRole, IUser } from '../../../../definition/IUser'; +import { apiDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; Meteor.methods({ - async 'authorization:removeUserFromRole'(roleName, username, scope) { - if (!Meteor.userId() || !hasPermission(Meteor.userId(), 'access-permissions')) { + async 'authorization:removeUserFromRole'(roleId, username, scope) { + const userId = Meteor.userId(); + + if (!userId || !hasPermission(userId, 'access-permissions')) { throw new Meteor.Error('error-action-not-allowed', 'Access permissions is not allowed', { method: 'authorization:removeUserFromRole', action: 'Accessing_permissions', }); } - if (!roleName || !_.isString(roleName) || !username || !_.isString(username)) { + if (!roleId || !_.isString(roleId) || !username || !_.isString(username)) { throw new Meteor.Error('error-invalid-arguments', 'Invalid arguments', { method: 'authorization:removeUserFromRole', }); } - const user = Meteor.users.findOne( - { - username, - }, - { - fields: { - _id: 1, - roles: 1, - }, + let role = await Roles.findOneById>(roleId, { projection: { _id: 1 } }); + if (!role) { + role = await Roles.findOneByName>(roleId, { projection: { _id: 1 } }); + if (!role) { + throw new Meteor.Error('error-invalid-role', 'Invalid Role', { + method: 'authorization:removeUserFromRole', + }); + } + + apiDeprecationLogger.warn( + `Calling authorization:removeUserFromRole with role names will be deprecated in future versions of Rocket.Chat`, + ); + } + + const user = Users.findOneByUsernameIgnoringCase(username, { + fields: { + _id: 1, + roles: 1, }, - ); + }) as Pick; if (!user || !user._id) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { @@ -40,7 +54,7 @@ Meteor.methods({ } // prevent removing last user from admin role - if (roleName === 'admin') { + if (role._id === 'admin') { const adminCount = Meteor.users .find({ roles: { @@ -58,11 +72,11 @@ Meteor.methods({ } } - const remove = await Roles.removeUserRoles(user._id, [roleName], scope); + const remove = await Roles.removeUserRoles(user._id, [role._id], scope); if (settings.get('UI_DisplayRoles')) { api.broadcast('user.roleUpdate', { type: 'removed', - _id: roleName, + _id: role._id, u: { _id: user._id, username, diff --git a/app/authorization/server/methods/saveRole.ts b/app/authorization/server/methods/saveRole.ts index 04f431ba9906..67a7d605c1a9 100644 --- a/app/authorization/server/methods/saveRole.ts +++ b/app/authorization/server/methods/saveRole.ts @@ -2,35 +2,47 @@ import { Meteor } from 'meteor/meteor'; import { settings } from '../../../settings/server'; import { hasPermission } from '../functions/hasPermission'; -import { api } from '../../../../server/sdk/api'; import { Roles } from '../../../models/server/raw'; +import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; +import { updateRoleAsync } from '../../../../server/lib/roles/updateRole'; +import { insertRoleAsync } from '../../../../server/lib/roles/insertRole'; +import { isRoleCreateProps } from '../../../../definition/rest/v1/roles'; Meteor.methods({ - async 'authorization:saveRole'(roleData) { - if (!Meteor.userId() || !hasPermission(Meteor.userId(), 'access-permissions')) { + async 'authorization:saveRole'(roleData: Record) { + methodDeprecationLogger.warn('authorization:saveRole will be deprecated in future versions of Rocket.Chat'); + const userId = Meteor.userId(); + + if (!userId || !hasPermission(userId, 'access-permissions')) { throw new Meteor.Error('error-action-not-allowed', 'Accessing permissions is not allowed', { method: 'authorization:saveRole', action: 'Accessing_permissions', }); } - if (!roleData.name) { - throw new Meteor.Error('error-role-name-required', 'Role name is required', { + if (!isRoleCreateProps(roleData)) { + throw new Meteor.Error('error-invalid-role-properties', 'The role properties are invalid.', { method: 'authorization:saveRole', }); } - if (['Users', 'Subscriptions'].includes(roleData.scope) === false) { - roleData.scope = 'Users'; - } + const role = { + description: roleData.description || '', + ...(roleData.mandatory2fa !== undefined && { mandatory2fa: roleData.mandatory2fa }), + name: roleData.name, + scope: roleData.scope || 'Users', + protected: false, + }; - const update = await Roles.createOrUpdate(roleData.name, roleData.scope, roleData.description, false, roleData.mandatory2fa); - if (settings.get('UI_DisplayRoles')) { - api.broadcast('user.roleUpdate', { - type: 'changed', - _id: roleData.name, - }); + const existingRole = await Roles.findOneByName(roleData.name, { projection: { _id: 1 } }); + const options = { + broadcastUpdate: settings.get('UI_DisplayRoles'), + }; + + if (existingRole) { + return updateRoleAsync(existingRole._id, role, options); } - return update; + + return insertRoleAsync(role); }, }); diff --git a/app/bot-helpers/server/index.js b/app/bot-helpers/server/index.js index 04c6d66445b2..0d6a0b68e889 100644 --- a/app/bot-helpers/server/index.js +++ b/app/bot-helpers/server/index.js @@ -43,12 +43,12 @@ class BotHelpers { return this[prop]; } - addUserToRole(userName, roleName) { - Meteor.call('authorization:addUserToRole', roleName, userName); + addUserToRole(userName, roleId) { + Meteor.call('authorization:addUserToRole', roleId, userName); } - removeUserFromRole(userName, roleName) { - Meteor.call('authorization:removeUserFromRole', roleName, userName); + removeUserFromRole(userName, roleId) { + Meteor.call('authorization:removeUserFromRole', roleId, userName); } addUserToRoom(userName, room) { diff --git a/app/channel-settings/server/functions/saveRoomName.js b/app/channel-settings/server/functions/saveRoomName.js index 159419151c27..05615a865075 100644 --- a/app/channel-settings/server/functions/saveRoomName.js +++ b/app/channel-settings/server/functions/saveRoomName.js @@ -2,9 +2,10 @@ import { Meteor } from 'meteor/meteor'; import { Rooms, Messages, Subscriptions } from '../../../models/server'; import { Integrations } from '../../../models/server/raw'; -import { roomTypes, getValidRoomName } from '../../../utils/server'; +import { getValidRoomName } from '../../../utils/server'; import { callbacks } from '../../../../lib/callbacks'; import { checkUsernameAvailability } from '../../../lib/server/functions'; +import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; const updateRoomName = (rid, displayName, isDiscussion) => { if (isDiscussion) { @@ -27,7 +28,7 @@ const updateRoomName = (rid, displayName, isDiscussion) => { export async function saveRoomName(rid, displayName, user, sendMessage = true) { const room = Rooms.findOneById(rid); - if (roomTypes.getConfig(room.t).preventRenaming()) { + if (roomCoordinator.getRoomDirectives(room.t)?.preventRenaming()) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { function: 'RocketChat.saveRoomdisplayName', }); diff --git a/app/channel-settings/server/functions/saveRoomReadOnly.js b/app/channel-settings/server/functions/saveRoomReadOnly.js index cee9d969efc9..7b7ed7bd0404 100644 --- a/app/channel-settings/server/functions/saveRoomReadOnly.js +++ b/app/channel-settings/server/functions/saveRoomReadOnly.js @@ -2,7 +2,6 @@ import { Meteor } from 'meteor/meteor'; import { Match } from 'meteor/check'; import { Rooms, Messages } from '../../../models'; -import { hasPermission } from '../../../authorization'; export const saveRoomReadOnly = function (rid, readOnly, user, sendMessage = true) { if (!Match.test(rid, String)) { @@ -10,7 +9,7 @@ export const saveRoomReadOnly = function (rid, readOnly, user, sendMessage = tru function: 'RocketChat.saveRoomReadOnly', }); } - const result = Rooms.setReadOnlyById(rid, readOnly, hasPermission); + const result = Rooms.setReadOnlyById(rid, readOnly); if (result && sendMessage) { readOnly ? Messages.createRoomSetReadOnlyByRoomIdAndUser(rid, user) : Messages.createRoomRemovedReadOnlyByRoomIdAndUser(rid, user); diff --git a/app/channel-settings/server/functions/saveRoomType.js b/app/channel-settings/server/functions/saveRoomType.js index 444f431073cf..25ecb15ec94e 100644 --- a/app/channel-settings/server/functions/saveRoomType.js +++ b/app/channel-settings/server/functions/saveRoomType.js @@ -4,7 +4,8 @@ import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { Rooms, Subscriptions, Messages } from '../../../models/server'; import { settings } from '../../../settings/server'; -import { roomTypes, RoomSettingsEnum } from '../../../utils/server'; +import { RoomSettingsEnum } from '../../../../definition/IRoomTypeConfig'; +import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; export const saveRoomType = function (rid, roomType, user, sendMessage = true) { if (!Match.test(rid, String)) { @@ -26,7 +27,7 @@ export const saveRoomType = function (rid, roomType, user, sendMessage = true) { }); } - if (!roomTypes.getConfig(room.t).allowRoomSettingChange(room, RoomSettingsEnum.TYPE)) { + if (!roomCoordinator.getRoomDirectives(room.t)?.allowRoomSettingChange(room, RoomSettingsEnum.TYPE)) { throw new Meteor.Error('error-direct-room', "Can't change type of direct rooms", { function: 'RocketChat.saveRoomType', }); diff --git a/app/channel-settings/server/methods/saveRoomSettings.js b/app/channel-settings/server/methods/saveRoomSettings.js index 59393abf7a28..29103c16ee8e 100644 --- a/app/channel-settings/server/methods/saveRoomSettings.js +++ b/app/channel-settings/server/methods/saveRoomSettings.js @@ -17,9 +17,10 @@ import { saveRoomSystemMessages } from '../functions/saveRoomSystemMessages'; import { saveRoomTokenpass } from '../functions/saveRoomTokens'; import { saveRoomEncrypted } from '../functions/saveRoomEncrypted'; import { saveStreamingOptions } from '../functions/saveStreamingOptions'; -import { RoomSettingsEnum, roomTypes } from '../../../utils'; import { Team } from '../../../../server/sdk'; import { TEAM_TYPE } from '../../../../definition/ITeam'; +import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; +import { RoomSettingsEnum } from '../../../../definition/IRoomTypeConfig'; const fields = [ 'roomAvatar', @@ -85,7 +86,7 @@ const validators = { }, encrypted({ userId, value, room, rid }) { if (value !== room.encrypted) { - if (!roomTypes.getConfig(room.t).allowRoomSettingChange(room, RoomSettingsEnum.E2E)) { + if (!roomCoordinator.getRoomDirectives(room.t)?.allowRoomSettingChange(room, RoomSettingsEnum.E2E)) { throw new Meteor.Error('error-action-not-allowed', 'Only groups or direct channels can enable encryption', { method: 'saveRoomSettings', action: 'Change_Room_Encrypted', diff --git a/app/chatpal-search/client/template/admin.js b/app/chatpal-search/client/template/admin.js index 5e4cc887601b..dd878d3e91e9 100644 --- a/app/chatpal-search/client/template/admin.js +++ b/app/chatpal-search/client/template/admin.js @@ -4,7 +4,7 @@ import { Template } from 'meteor/templating'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { settings } from '../../../settings'; -import { hasRole } from '../../../authorization'; +import { hasPermission } from '../../../authorization'; import { dispatchToastMessage } from '../../../../client/lib/toast'; import { validateEmail } from '../../../../lib/emailValidator'; @@ -83,7 +83,7 @@ Template.ChatpalAdmin.helpers({ return Template.instance().apiKey.get(); }, isAdmin() { - return hasRole(Meteor.userId(), 'admin'); + return hasPermission('manage-chatpal'); }, tac() { return Template.instance().tac.get(); diff --git a/app/chatpal-search/client/template/result.js b/app/chatpal-search/client/template/result.js index 63a7209cdb05..b96df425c3b4 100644 --- a/app/chatpal-search/client/template/result.js +++ b/app/chatpal-search/client/template/result.js @@ -2,11 +2,12 @@ import { ReactiveVar } from 'meteor/reactive-var'; import { Template } from 'meteor/templating'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; -import { roomTypes, getURL } from '../../../utils'; +import { getURL } from '../../../utils'; import { Subscriptions } from '../../../models'; import { getUserAvatarURL as getAvatarUrl } from '../../../utils/lib/getUserAvatarURL'; import { formatTime } from '../../../../client/lib/utils/formatTime'; import { formatDate } from '../../../../client/lib/utils/formatDate'; +import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator'; const getDMUrl = (username) => getURL(`/direct/${username}`); @@ -101,15 +102,15 @@ Template.ChatpalSearchSingleMessage.helpers({ if (room && room.t === 'd') { return 'at'; } - return roomTypes.getIcon(room); + return roomCoordinator.getIcon(room); }, roomLink() { - return roomTypes.getRouteLink(this.r.t, this.r); + return roomCoordinator.getRouteLink(this.r.t, this.r); }, roomName() { - return roomTypes.getRoomName(this.r.t, this.r); + return roomCoordinator.getRoomName(this.r.t, this.r); }, roomNotSubscribed() { @@ -131,10 +132,10 @@ Template.ChatpalSearchSingleRoom.helpers({ if (this.t === 'd') { return 'at'; } - return roomTypes.getIcon(this); + return roomCoordinator.getIcon(this); }, roomLink() { - return roomTypes.getRouteLink(this.t, this); + return roomCoordinator.getRouteLink(this.t, this); }, roomNotSubscribed() { const subscription = Subscriptions.findOne({ rid: this.rid }); diff --git a/app/cloud/server/functions/retrieveRegistrationStatus.js b/app/cloud/server/functions/retrieveRegistrationStatus.js index 562238c466cb..4a6134e264ab 100644 --- a/app/cloud/server/functions/retrieveRegistrationStatus.js +++ b/app/cloud/server/functions/retrieveRegistrationStatus.js @@ -8,14 +8,12 @@ export function retrieveRegistrationStatus() { workspaceId: settings.get('Cloud_Workspace_Id'), uniqueId: settings.get('uniqueID'), token: '', - email: '', + email: settings.get('Organization_Email'), }; - const firstUser = Users.getOldest({ emails: 1 }); - info.email = firstUser && firstUser.emails && firstUser.emails[0].address; - - if (settings.get('Organization_Email')) { - info.email = settings.get('Organization_Email'); + if (!info.email) { + const firstUser = Users.getOldest({ emails: 1 }); + info.email = firstUser && firstUser.emails && firstUser.emails[0].address; } return info; diff --git a/app/cloud/server/functions/syncWorkspace.js b/app/cloud/server/functions/syncWorkspace.js index c937bcb6b72f..6424c5ea867e 100644 --- a/app/cloud/server/functions/syncWorkspace.js +++ b/app/cloud/server/functions/syncWorkspace.js @@ -56,6 +56,10 @@ export async function syncWorkspace(reconnectCheck = false) { Settings.updateValueById('Cloud_Workspace_PublicKey', data.publicKey); } + if (data.trial?.trialId) { + Settings.updateValueById('Cloud_Workspace_Had_Trial', true); + } + if (data.nps) { const { id: npsId, expireAt } = data.nps; diff --git a/app/crowd/server/crowd.js b/app/crowd/server/crowd.js index 9f806d3246a5..535e4e130b74 100644 --- a/app/crowd/server/crowd.js +++ b/app/crowd/server/crowd.js @@ -7,7 +7,7 @@ import { Logger } from '../../logger/server'; import { _setRealName } from '../../lib/server'; import { Users } from '../../models/server'; import { settings } from '../../settings/server'; -import { hasRole } from '../../authorization/server'; +import { hasPermission } from '../../authorization/server'; import { deleteUser } from '../../lib/server/functions'; import { setUserActiveStatus } from '../../lib/server/functions/setUserActiveStatus'; @@ -344,7 +344,7 @@ Meteor.methods({ }); } - if (!hasRole(user._id, 'admin')) { + if (!hasPermission(user._id, 'test-admin-options')) { throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'crowd_test_connection', }); @@ -375,7 +375,7 @@ Meteor.methods({ throw new Meteor.Error('crowd_disabled'); } - if (!hasRole(user._id, 'admin')) { + if (!hasPermission(user._id, 'sync-auth-services-users')) { throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'crowd_sync_users', }); diff --git a/app/custom-oauth/client/custom_oauth_client.js b/app/custom-oauth/client/custom_oauth_client.js index b500960fbcf4..1e997db2361b 100644 --- a/app/custom-oauth/client/custom_oauth_client.js +++ b/app/custom-oauth/client/custom_oauth_client.js @@ -50,6 +50,7 @@ export class CustomOAuth { this.serverURL = options.serverURL; this.authorizePath = options.authorizePath; this.scope = options.scope; + this.responseType = options.responseType || 'code'; if (!isURL(this.authorizePath)) { this.authorizePath = this.serverURL + this.authorizePath; @@ -95,7 +96,7 @@ export class CustomOAuth { const loginUrl = `${this.authorizePath}${separator}client_id=${config.clientId}&redirect_uri=${encodeURIComponent( OAuth._redirectUri(this.name, config), - )}&response_type=code` + + )}&response_type=${encodeURIComponent(this.responseType)}` + `&state=${encodeURIComponent(OAuth._stateParam(loginStyle, credentialToken, options.redirectUrl))}&scope=${encodeURIComponent( this.scope, )}`; diff --git a/app/custom-oauth/server/custom_oauth_server.d.ts b/app/custom-oauth/server/custom_oauth_server.d.ts new file mode 100644 index 000000000000..e0554f0b609a --- /dev/null +++ b/app/custom-oauth/server/custom_oauth_server.d.ts @@ -0,0 +1,5 @@ +export class CustomOAuth { + constructor(name: string, options: Record); + + getIdentity(accessToken: string, query: Record): any; +} diff --git a/app/custom-oauth/server/custom_oauth_server.js b/app/custom-oauth/server/custom_oauth_server.js index 0ac7588aea5a..9281e7be80ae 100644 --- a/app/custom-oauth/server/custom_oauth_server.js +++ b/app/custom-oauth/server/custom_oauth_server.js @@ -193,7 +193,7 @@ export class CustomOAuth { const self = this; OAuth.registerService(this.name, 2, null, (query) => { const response = self.getAccessToken(query); - const identity = self.getIdentity(response.access_token); + const identity = self.getIdentity(response.access_token, query); const serviceData = { _OAuthCustom: true, diff --git a/app/custom-sounds/client/lib/CustomSounds.js b/app/custom-sounds/client/lib/CustomSounds.js index 7cea36be304c..07c83303cb32 100644 --- a/app/custom-sounds/client/lib/CustomSounds.js +++ b/app/custom-sounds/client/lib/CustomSounds.js @@ -33,6 +33,12 @@ class CustomSoundsClass { extension: 'mp3', src: getURL('sounds/seasons.mp3'), }); + this.add({ + _id: 'telephone', + name: 'Telephone', + extension: 'mp3', + src: getURL('sounds/telephone.mp3'), + }); } add(sound) { diff --git a/app/custom-sounds/server/methods/deleteCustomSound.js b/app/custom-sounds/server/methods/deleteCustomSound.js index cb1227ce29c5..9f935e1d3df4 100644 --- a/app/custom-sounds/server/methods/deleteCustomSound.js +++ b/app/custom-sounds/server/methods/deleteCustomSound.js @@ -1,8 +1,8 @@ import { Meteor } from 'meteor/meteor'; import { CustomSounds } from '../../../models/server/raw'; -import { hasPermission } from '../../../authorization'; -import { Notifications } from '../../../notifications'; +import { hasPermission } from '../../../authorization/server'; +import { api } from '../../../../server/sdk/api'; import { RocketChatFileCustomSoundsInstance } from '../startup/custom-sounds'; Meteor.methods({ @@ -23,7 +23,7 @@ Meteor.methods({ RocketChatFileCustomSoundsInstance.deleteFile(`${sound._id}.${sound.extension}`); await CustomSounds.removeById(_id); - Notifications.notifyAll('deleteCustomSound', { soundData: sound }); + api.broadcast('notify.deleteCustomSound', { soundData: sound }); return true; }, diff --git a/app/custom-sounds/server/methods/insertOrUpdateSound.js b/app/custom-sounds/server/methods/insertOrUpdateSound.js index 6aed25e4ead7..fa36d4165b2d 100644 --- a/app/custom-sounds/server/methods/insertOrUpdateSound.js +++ b/app/custom-sounds/server/methods/insertOrUpdateSound.js @@ -2,9 +2,9 @@ import { Meteor } from 'meteor/meteor'; import s from 'underscore.string'; import { check } from 'meteor/check'; -import { hasPermission } from '../../../authorization'; +import { hasPermission } from '../../../authorization/server'; import { CustomSounds } from '../../../models/server/raw'; -import { Notifications } from '../../../notifications'; +import { api } from '../../../../server/sdk/api'; import { RocketChatFileCustomSoundsInstance } from '../startup/custom-sounds'; Meteor.methods({ @@ -71,7 +71,7 @@ Meteor.methods({ if (soundData.name !== soundData.previousName) { await CustomSounds.setName(soundData._id, soundData.name); - Notifications.notifyAll('updateCustomSound', { soundData }); + api.broadcast('notify.updateCustomSound', { soundData }); } return soundData._id; diff --git a/app/custom-sounds/server/methods/uploadCustomSound.js b/app/custom-sounds/server/methods/uploadCustomSound.js index c1c13d285d0f..633903348702 100644 --- a/app/custom-sounds/server/methods/uploadCustomSound.js +++ b/app/custom-sounds/server/methods/uploadCustomSound.js @@ -1,8 +1,8 @@ import { Meteor } from 'meteor/meteor'; -import { hasPermission } from '../../../authorization'; -import { Notifications } from '../../../notifications'; -import { RocketChatFile } from '../../../file'; +import { hasPermission } from '../../../authorization/server'; +import { api } from '../../../../server/sdk/api'; +import { RocketChatFile } from '../../../file/server'; import { RocketChatFileCustomSoundsInstance } from '../startup/custom-sounds'; Meteor.methods({ @@ -18,7 +18,7 @@ Meteor.methods({ const ws = RocketChatFileCustomSoundsInstance.createWriteStream(`${soundData._id}.${soundData.extension}`, contentType); ws.on( 'end', - Meteor.bindEnvironment(() => Meteor.setTimeout(() => Notifications.notifyAll('updateCustomSound', { soundData }), 500)), + setTimeout(() => api.broadcast('notify.updateCustomSound', { soundData }), 500), ); rs.pipe(ws); diff --git a/app/discussion/client/createDiscussionMessageAction.js b/app/discussion/client/createDiscussionMessageAction.js index fa7517d29314..7b1dadd5f67e 100644 --- a/app/discussion/client/createDiscussionMessageAction.js +++ b/app/discussion/client/createDiscussionMessageAction.js @@ -5,9 +5,9 @@ import { settings } from '../../settings/client'; import { hasPermission } from '../../authorization/client'; import { MessageAction } from '../../ui-utils/client'; import { messageArgs } from '../../ui-utils/client/lib/messageArgs'; -import { roomTypes } from '../../utils/client'; import { imperativeModal } from '../../../client/lib/imperativeModal'; import CreateDiscussion from '../../../client/components/CreateDiscussion/CreateDiscussion'; +import { roomCoordinator } from '../../../client/lib/rooms/roomCoordinator'; Meteor.startup(function () { Tracker.autorun(() => { @@ -49,7 +49,7 @@ Meteor.startup(function () { if (!subscription) { return false; } - const isLivechatRoom = roomTypes.isLivechatRoom(room.t); + const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t); if (isLivechatRoom) { return false; } diff --git a/app/discussion/client/index.js b/app/discussion/client/index.js index c209596ea44a..25f06f1a7bb4 100644 --- a/app/discussion/client/index.js +++ b/app/discussion/client/index.js @@ -3,5 +3,3 @@ import './lib/messageTypes/discussionMessage'; import './createDiscussionMessageAction'; import './discussionFromMessageBox'; import './tabBar'; - -import '../lib/discussionRoomType'; diff --git a/app/discussion/lib/discussionRoomType.js b/app/discussion/lib/discussionRoomType.js deleted file mode 100644 index da8219a17be0..000000000000 --- a/app/discussion/lib/discussionRoomType.js +++ /dev/null @@ -1,13 +0,0 @@ -import { RoomTypeConfig, roomTypes } from '../../utils'; - -export class DiscussionRoomType extends RoomTypeConfig { - constructor() { - super({ - identifier: 't', - order: 25, - label: 'Discussion', - }); - } -} - -roomTypes.add(new DiscussionRoomType()); diff --git a/app/discussion/server/index.js b/app/discussion/server/index.js index 92e47178450a..4ae84cfb1c55 100644 --- a/app/discussion/server/index.js +++ b/app/discussion/server/index.js @@ -5,6 +5,3 @@ import './hooks/propagateDiscussionMetadata'; // Methods import './methods/createDiscussion'; - -// Lib -import '../lib/discussionRoomType'; diff --git a/app/discussion/server/methods/createDiscussion.js b/app/discussion/server/methods/createDiscussion.js index 6868608d6c59..212201f2b293 100644 --- a/app/discussion/server/methods/createDiscussion.js +++ b/app/discussion/server/methods/createDiscussion.js @@ -7,8 +7,8 @@ import { hasAtLeastOnePermission, canSendMessage } from '../../../authorization/ import { Messages, Rooms } from '../../../models/server'; import { createRoom, addUserToRoom, sendMessage, attachMessage } from '../../../lib/server'; import { settings } from '../../../settings/server'; -import { roomTypes } from '../../../utils/server'; import { callbacks } from '../../../../lib/callbacks'; +import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; const getParentRoom = (rid) => { const room = Rooms.findOne(rid); @@ -111,7 +111,7 @@ const create = ({ prid, pmid, t_name, reply, users, user, encrypted }) => { // auto invite the replied message owner const invitedUsers = message ? [message.u.username, ...users] : users; - const type = roomTypes.getConfig(p_room.t).getDiscussionType(); + const type = roomCoordinator.getRoomDirectives(p_room.t)?.getDiscussionType(); const description = p_room.encrypted ? '' : message.msg; const topic = p_room.name; diff --git a/app/dolphin/lib/common.js b/app/dolphin/lib/common.js index ddaacc16b2a2..d9a901cedda0 100644 --- a/app/dolphin/lib/common.js +++ b/app/dolphin/lib/common.js @@ -30,7 +30,7 @@ function DolphinOnCreateUser(options, user) { if (Meteor.isServer) { Meteor.startup(() => - settings.get('Accounts_OAuth_Dolphin_URL', (key, value) => { + settings.watch('Accounts_OAuth_Dolphin_URL', (value) => { config.serverURL = value; return Dolphin.configure(config); }), diff --git a/app/drupal/lib/common.js b/app/drupal/lib/common.js index 4eea0cf99088..9090b3d75e2e 100644 --- a/app/drupal/lib/common.js +++ b/app/drupal/lib/common.js @@ -27,7 +27,7 @@ const Drupal = new CustomOAuth('drupal', config); if (Meteor.isServer) { Meteor.startup(function () { - settings.get('API_Drupal_URL', function (key, value) { + settings.watch('API_Drupal_URL', function (value) { config.serverURL = value; Drupal.configure(config); }); diff --git a/app/e2e/client/helper.js b/app/e2e/client/helper.js index d2c40ecd626b..8aec8db794d3 100644 --- a/app/e2e/client/helper.js +++ b/app/e2e/client/helper.js @@ -2,6 +2,8 @@ import ByteBuffer from 'bytebuffer'; +import { getRandomFraction } from '../../../lib/random'; + const StaticArrayBufferProto = new ArrayBuffer().__proto__; export function toString(thing) { @@ -122,6 +124,20 @@ export async function readFileAsArrayBuffer(file) { }); } +export async function generateMnemonicPhrase(n, sep = ' ') { + const { default: wordList } = await import('./wordList'); + const result = new Array(n); + let len = wordList.length; + const taken = new Array(len); + + while (n--) { + const x = Math.floor(getRandomFraction() * len); + result[n] = wordList[x in taken ? taken[x] : x]; + taken[x] = --len in taken ? taken[len] : len; + } + return result.join(sep); +} + export class Deferred { constructor() { const p = new Promise((resolve, reject) => { diff --git a/app/e2e/client/rocketchat.e2e.js b/app/e2e/client/rocketchat.e2e.js index 913797f53f4a..88935931b832 100644 --- a/app/e2e/client/rocketchat.e2e.js +++ b/app/e2e/client/rocketchat.e2e.js @@ -1,5 +1,4 @@ import { Meteor } from 'meteor/meteor'; -import { Random } from 'meteor/random'; import { ReactiveVar } from 'meteor/reactive-var'; import { EJSON } from 'meteor/ejson'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; @@ -18,6 +17,7 @@ import { importRSAKey, importRawKey, deriveKey, + generateMnemonicPhrase, } from './helper'; import * as banners from '../../../client/lib/banners'; import { Rooms, Subscriptions, Messages } from '../../models/client'; @@ -136,7 +136,7 @@ class E2E extends Emitter { if (!this.db_public_key || !this.db_private_key) { await call('e2e.setUserPublicAndPrivateKeys', { public_key: Meteor._localStorage.getItem('public_key'), - private_key: await this.encodePrivateKey(Meteor._localStorage.getItem('private_key'), this.createRandomPassword()), + private_key: await this.encodePrivateKey(Meteor._localStorage.getItem('private_key'), await this.createRandomPassword()), }); } @@ -256,8 +256,8 @@ class E2E extends Emitter { call('e2e.requestSubscriptionKeys'); } - createRandomPassword() { - const randomPassword = `${Random.id(3)}-${Random.id(3)}-${Random.id(3)}`.toLowerCase(); + async createRandomPassword() { + const randomPassword = await generateMnemonicPhrase(5); Meteor._localStorage.setItem('e2e.randomPassword', randomPassword); return randomPassword; } diff --git a/app/e2e/client/rocketchat.e2e.room.js b/app/e2e/client/rocketchat.e2e.room.js index 0f2344dbe144..223644f56796 100644 --- a/app/e2e/client/rocketchat.e2e.room.js +++ b/app/e2e/client/rocketchat.e2e.room.js @@ -24,10 +24,11 @@ import { } from './helper'; import { Notifications } from '../../notifications/client'; import { Rooms, Subscriptions, Messages } from '../../models/client'; -import { roomTypes, RoomSettingsEnum } from '../../utils/client'; import { log, logError } from './logger'; import { E2ERoomState } from './E2ERoomState'; import { call } from '../../../client/lib/utils/call'; +import { roomCoordinator } from '../../../client/lib/rooms/roomCoordinator'; +import { RoomSettingsEnum } from '../../../definition/IRoomTypeConfig'; const KEY_ID = Symbol('keyID'); const PAUSED = Symbol('PAUSED'); @@ -145,7 +146,7 @@ export class E2ERoom extends Emitter { this.setState(E2ERoomState.KEYS_RECEIVED); } - async shouldConvertSentMessages() { + async shouldConvertSentMessages(message) { if (!this.isReady() || this[PAUSED]) { return false; } @@ -156,6 +157,10 @@ export class E2ERoom extends Emitter { }); } + if (message.msg[0] === '/') { + return false; + } + return true; } @@ -245,7 +250,7 @@ export class E2ERoom extends Emitter { } isSupportedRoomType(type) { - return roomTypes.getConfig(type).allowRoomSettingChange({}, RoomSettingsEnum.E2E); + return roomCoordinator.getRoomDirectives(type)?.allowRoomSettingChange({}, RoomSettingsEnum.E2E); } async importGroupKey(groupKey) { diff --git a/app/e2e/client/wordList.ts b/app/e2e/client/wordList.ts new file mode 100644 index 000000000000..f2930b6cb969 --- /dev/null +++ b/app/e2e/client/wordList.ts @@ -0,0 +1,1635 @@ +export default [ + 'acrobat', + 'africa', + 'alaska', + 'albert', + 'albino', + 'album', + 'alcohol', + 'alex', + 'alpha', + 'amadeus', + 'amanda', + 'amazon', + 'america', + 'analog', + 'animal', + 'antenna', + 'antonio', + 'apollo', + 'april', + 'aroma', + 'artist', + 'aspirin', + 'athlete', + 'atlas', + 'banana', + 'bandit', + 'banjo', + 'bikini', + 'bingo', + 'bonus', + 'camera', + 'canada', + 'carbon', + 'casino', + 'catalog', + 'cinema', + 'citizen', + 'cobra', + 'comet', + 'compact', + 'complex', + 'context', + 'credit', + 'critic', + 'crystal', + 'culture', + 'david', + 'delta', + 'dialog', + 'diploma', + 'doctor', + 'domino', + 'dragon', + 'drama', + 'extra', + 'fabric', + 'final', + 'focus', + 'forum', + 'galaxy', + 'gallery', + 'global', + 'harmony', + 'hotel', + 'humor', + 'index', + 'japan', + 'kilo', + 'lemon', + 'liter', + 'lotus', + 'mango', + 'melon', + 'menu', + 'meter', + 'metro', + 'mineral', + 'model', + 'music', + 'object', + 'piano', + 'pirate', + 'plastic', + 'radio', + 'report', + 'signal', + 'sport', + 'studio', + 'subject', + 'super', + 'tango', + 'taxi', + 'tempo', + 'tennis', + 'textile', + 'tokyo', + 'total', + 'tourist', + 'video', + 'visa', + 'academy', + 'alfred', + 'atlanta', + 'atomic', + 'barbara', + 'bazaar', + 'brother', + 'budget', + 'cabaret', + 'cadet', + 'candle', + 'capsule', + 'caviar', + 'channel', + 'chapter', + 'circle', + 'cobalt', + 'comrade', + 'condor', + 'crimson', + 'cyclone', + 'darwin', + 'declare', + 'denver', + 'desert', + 'divide', + 'dolby', + 'domain', + 'double', + 'eagle', + 'echo', + 'eclipse', + 'editor', + 'educate', + 'edward', + 'effect', + 'electra', + 'emerald', + 'emotion', + 'empire', + 'eternal', + 'evening', + 'exhibit', + 'expand', + 'explore', + 'extreme', + 'ferrari', + 'forget', + 'freedom', + 'friday', + 'fuji', + 'galileo', + 'genesis', + 'gravity', + 'habitat', + 'hamlet', + 'harlem', + 'helium', + 'holiday', + 'hunter', + 'ibiza', + 'iceberg', + 'imagine', + 'infant', + 'isotope', + 'jackson', + 'jamaica', + 'jasmine', + 'java', + 'jessica', + 'kitchen', + 'lazarus', + 'letter', + 'license', + 'lithium', + 'loyal', + 'lucky', + 'magenta', + 'manual', + 'marble', + 'maxwell', + 'mayor', + 'monarch', + 'monday', + 'money', + 'morning', + 'mother', + 'mystery', + 'native', + 'nectar', + 'nelson', + 'network', + 'nikita', + 'nobel', + 'nobody', + 'nominal', + 'norway', + 'nothing', + 'number', + 'october', + 'office', + 'oliver', + 'opinion', + 'option', + 'order', + 'outside', + 'package', + 'pandora', + 'panther', + 'papa', + 'pattern', + 'pedro', + 'pencil', + 'people', + 'phantom', + 'philips', + 'pioneer', + 'pluto', + 'podium', + 'portal', + 'potato', + 'process', + 'proxy', + 'pupil', + 'python', + 'quality', + 'quarter', + 'quiet', + 'rabbit', + 'radical', + 'radius', + 'rainbow', + 'ramirez', + 'ravioli', + 'raymond', + 'respect', + 'respond', + 'result', + 'resume', + 'richard', + 'river', + 'roger', + 'roman', + 'rondo', + 'sabrina', + 'salary', + 'salsa', + 'sample', + 'samuel', + 'saturn', + 'savage', + 'scarlet', + 'scorpio', + 'sector', + 'serpent', + 'shampoo', + 'sharon', + 'silence', + 'simple', + 'society', + 'sonar', + 'sonata', + 'soprano', + 'sparta', + 'spider', + 'sponsor', + 'abraham', + 'action', + 'active', + 'actor', + 'adam', + 'address', + 'admiral', + 'adrian', + 'agenda', + 'agent', + 'airline', + 'airport', + 'alabama', + 'aladdin', + 'alarm', + 'algebra', + 'alibi', + 'alice', + 'alien', + 'almond', + 'alpine', + 'amber', + 'amigo', + 'ammonia', + 'analyze', + 'anatomy', + 'angel', + 'annual', + 'answer', + 'apple', + 'archive', + 'arctic', + 'arena', + 'arizona', + 'armada', + 'arnold', + 'arsenal', + 'arthur', + 'asia', + 'aspect', + 'athena', + 'audio', + 'august', + 'austria', + 'avenue', + 'average', + 'axiom', + 'aztec', + 'bagel', + 'baker', + 'balance', + 'ballad', + 'ballet', + 'bambino', + 'bamboo', + 'baron', + 'basic', + 'basket', + 'battery', + 'belgium', + 'benefit', + 'berlin', + 'bermuda', + 'bernard', + 'bicycle', + 'binary', + 'biology', + 'bishop', + 'blitz', + 'block', + 'blonde', + 'bonjour', + 'boris', + 'boston', + 'bottle', + 'boxer', + 'brandy', + 'bravo', + 'brazil', + 'bridge', + 'british', + 'bronze', + 'brown', + 'bruce', + 'bruno', + 'brush', + 'burger', + 'burma', + 'cabinet', + 'cactus', + 'cafe', + 'cairo', + 'calypso', + 'camel', + 'campus', + 'canal', + 'cannon', + 'canoe', + 'cantina', + 'canvas', + 'canyon', + 'capital', + 'caramel', + 'caravan', + 'career', + 'cargo', + 'carlo', + 'carol', + 'carpet', + 'cartel', + 'cartoon', + 'castle', + 'castro', + 'cecilia', + 'cement', + 'center', + 'century', + 'ceramic', + 'chamber', + 'chance', + 'change', + 'chaos', + 'charlie', + 'charm', + 'charter', + 'cheese', + 'chef', + 'chemist', + 'cherry', + 'chess', + 'chicago', + 'chicken', + 'chief', + 'china', + 'cigar', + 'circus', + 'city', + 'clara', + 'classic', + 'claudia', + 'clean', + 'client', + 'climax', + 'clinic', + 'clock', + 'club', + 'cockpit', + 'coconut', + 'cola', + 'collect', + 'colombo', + 'colony', + 'color', + 'combat', + 'comedy', + 'command', + 'company', + 'concert', + 'connect', + 'consul', + 'contact', + 'contour', + 'control', + 'convert', + 'copy', + 'corner', + 'corona', + 'correct', + 'cosmos', + 'couple', + 'courage', + 'cowboy', + 'craft', + 'crash', + 'cricket', + 'crown', + 'cuba', + 'dallas', + 'dance', + 'daniel', + 'decade', + 'decimal', + 'degree', + 'delete', + 'deliver', + 'delphi', + 'deluxe', + 'demand', + 'demo', + 'denmark', + 'derby', + 'design', + 'detect', + 'develop', + 'diagram', + 'diamond', + 'diana', + 'diego', + 'diesel', + 'diet', + 'digital', + 'dilemma', + 'direct', + 'disco', + 'disney', + 'distant', + 'dollar', + 'dolphin', + 'donald', + 'drink', + 'driver', + 'dublin', + 'duet', + 'dynamic', + 'earth', + 'east', + 'ecology', + 'economy', + 'edgar', + 'egypt', + 'elastic', + 'elegant', + 'element', + 'elite', + 'elvis', + 'email', + 'empty', + 'energy', + 'engine', + 'english', + 'episode', + 'equator', + 'escape', + 'escort', + 'ethnic', + 'europe', + 'everest', + 'evident', + 'exact', + 'example', + 'exit', + 'exotic', + 'export', + 'express', + 'factor', + 'falcon', + 'family', + 'fantasy', + 'fashion', + 'fiber', + 'fiction', + 'fidel', + 'fiesta', + 'figure', + 'film', + 'filter', + 'finance', + 'finish', + 'finland', + 'first', + 'flag', + 'flash', + 'florida', + 'flower', + 'fluid', + 'flute', + 'folio', + 'ford', + 'forest', + 'formal', + 'formula', + 'fortune', + 'forward', + 'fragile', + 'france', + 'frank', + 'fresh', + 'friend', + 'frozen', + 'future', + 'gabriel', + 'gamma', + 'garage', + 'garcia', + 'garden', + 'garlic', + 'gemini', + 'general', + 'genetic', + 'genius', + 'germany', + 'gloria', + 'gold', + 'golf', + 'gondola', + 'gong', + 'good', + 'gordon', + 'gorilla', + 'grand', + 'granite', + 'graph', + 'green', + 'group', + 'guide', + 'guitar', + 'guru', + 'hand', + 'happy', + 'harbor', + 'harvard', + 'havana', + 'hawaii', + 'helena', + 'hello', + 'henry', + 'hilton', + 'history', + 'horizon', + 'house', + 'human', + 'icon', + 'idea', + 'igloo', + 'igor', + 'image', + 'impact', + 'import', + 'india', + 'indigo', + 'input', + 'insect', + 'instant', + 'iris', + 'italian', + 'jacket', + 'jacob', + 'jaguar', + 'janet', + 'jargon', + 'jazz', + 'jeep', + 'john', + 'joker', + 'jordan', + 'judo', + 'jumbo', + 'june', + 'jungle', + 'junior', + 'jupiter', + 'karate', + 'karma', + 'kayak', + 'kermit', + 'king', + 'koala', + 'korea', + 'labor', + 'lady', + 'lagoon', + 'laptop', + 'laser', + 'latin', + 'lava', + 'lecture', + 'left', + 'legal', + 'level', + 'lexicon', + 'liberal', + 'libra', + 'lily', + 'limbo', + 'limit', + 'linda', + 'linear', + 'lion', + 'liquid', + 'little', + 'llama', + 'lobby', + 'lobster', + 'local', + 'logic', + 'logo', + 'lola', + 'london', + 'lucas', + 'lunar', + 'machine', + 'macro', + 'madam', + 'madonna', + 'madrid', + 'maestro', + 'magic', + 'magnet', + 'magnum', + 'mailbox', + 'major', + 'mama', + 'mambo', + 'manager', + 'manila', + 'marco', + 'marina', + 'market', + 'mars', + 'martin', + 'marvin', + 'mary', + 'master', + 'matrix', + 'maximum', + 'media', + 'medical', + 'mega', + 'melody', + 'memo', + 'mental', + 'mentor', + 'mercury', + 'message', + 'metal', + 'meteor', + 'method', + 'mexico', + 'miami', + 'micro', + 'milk', + 'million', + 'minimum', + 'minus', + 'minute', + 'miracle', + 'mirage', + 'miranda', + 'mister', + 'mixer', + 'mobile', + 'modem', + 'modern', + 'modular', + 'moment', + 'monaco', + 'monica', + 'monitor', + 'mono', + 'monster', + 'montana', + 'morgan', + 'motel', + 'motif', + 'motor', + 'mozart', + 'multi', + 'museum', + 'mustang', + 'natural', + 'neon', + 'nepal', + 'neptune', + 'nerve', + 'neutral', + 'nevada', + 'news', + 'next', + 'ninja', + 'nirvana', + 'normal', + 'nova', + 'novel', + 'nuclear', + 'numeric', + 'nylon', + 'oasis', + 'observe', + 'ocean', + 'octopus', + 'olivia', + 'olympic', + 'omega', + 'opera', + 'optic', + 'optimal', + 'orange', + 'orbit', + 'organic', + 'orient', + 'origin', + 'orlando', + 'oscar', + 'oxford', + 'oxygen', + 'ozone', + 'pablo', + 'pacific', + 'pagoda', + 'palace', + 'pamela', + 'panama', + 'pancake', + 'panda', + 'panel', + 'panic', + 'paradox', + 'pardon', + 'paris', + 'parker', + 'parking', + 'parody', + 'partner', + 'passage', + 'passive', + 'pasta', + 'pastel', + 'patent', + 'patient', + 'patriot', + 'patrol', + 'pegasus', + 'pelican', + 'penguin', + 'pepper', + 'percent', + 'perfect', + 'perfume', + 'period', + 'permit', + 'person', + 'peru', + 'phone', + 'photo', + 'picasso', + 'picnic', + 'picture', + 'pigment', + 'pilgrim', + 'pilot', + 'pixel', + 'pizza', + 'planet', + 'plasma', + 'plaza', + 'pocket', + 'poem', + 'poetic', + 'poker', + 'polaris', + 'police', + 'politic', + 'polo', + 'polygon', + 'pony', + 'popcorn', + 'popular', + 'postage', + 'precise', + 'prefix', + 'premium', + 'present', + 'price', + 'prince', + 'printer', + 'prism', + 'private', + 'prize', + 'product', + 'profile', + 'program', + 'project', + 'protect', + 'proton', + 'public', + 'pulse', + 'puma', + 'pump', + 'pyramid', + 'queen', + 'radar', + 'ralph', + 'random', + 'rapid', + 'rebel', + 'record', + 'recycle', + 'reflex', + 'reform', + 'regard', + 'regular', + 'relax', + 'reptile', + 'reverse', + 'ricardo', + 'right', + 'ringo', + 'risk', + 'ritual', + 'robert', + 'robot', + 'rocket', + 'rodeo', + 'romeo', + 'royal', + 'russian', + 'safari', + 'salad', + 'salami', + 'salmon', + 'salon', + 'salute', + 'samba', + 'sandra', + 'santana', + 'sardine', + 'school', + 'scoop', + 'scratch', + 'screen', + 'script', + 'scroll', + 'second', + 'secret', + 'section', + 'segment', + 'select', + 'seminar', + 'senator', + 'senior', + 'sensor', + 'serial', + 'service', + 'shadow', + 'sharp', + 'sheriff', + 'shock', + 'short', + 'shrink', + 'sierra', + 'silicon', + 'silk', + 'silver', + 'similar', + 'simon', + 'single', + 'siren', + 'slang', + 'slogan', + 'smart', + 'smoke', + 'snake', + 'social', + 'soda', + 'solar', + 'solid', + 'solo', + 'sonic', + 'source', + 'soviet', + 'special', + 'speed', + 'sphere', + 'spiral', + 'spirit', + 'spring', + 'static', + 'status', + 'stereo', + 'stone', + 'stop', + 'street', + 'strong', + 'student', + 'style', + 'sultan', + 'susan', + 'sushi', + 'suzuki', + 'switch', + 'symbol', + 'system', + 'tactic', + 'tahiti', + 'talent', + 'tarzan', + 'telex', + 'texas', + 'theory', + 'thermos', + 'tiger', + 'titanic', + 'tomato', + 'topic', + 'tornado', + 'toronto', + 'torpedo', + 'totem', + 'tractor', + 'traffic', + 'transit', + 'trapeze', + 'travel', + 'tribal', + 'trick', + 'trident', + 'trilogy', + 'tripod', + 'tropic', + 'trumpet', + 'tulip', + 'tuna', + 'turbo', + 'twist', + 'ultra', + 'uniform', + 'union', + 'uranium', + 'vacuum', + 'valid', + 'vampire', + 'vanilla', + 'vatican', + 'velvet', + 'ventura', + 'venus', + 'vertigo', + 'veteran', + 'victor', + 'vienna', + 'viking', + 'village', + 'vincent', + 'violet', + 'violin', + 'virtual', + 'virus', + 'vision', + 'visitor', + 'visual', + 'vitamin', + 'viva', + 'vocal', + 'vodka', + 'volcano', + 'voltage', + 'volume', + 'voyage', + 'water', + 'weekend', + 'welcome', + 'western', + 'window', + 'winter', + 'wizard', + 'wolf', + 'world', + 'xray', + 'yankee', + 'yoga', + 'yogurt', + 'yoyo', + 'zebra', + 'zero', + 'zigzag', + 'zipper', + 'zodiac', + 'zoom', + 'acid', + 'adios', + 'agatha', + 'alamo', + 'alert', + 'almanac', + 'aloha', + 'andrea', + 'anita', + 'arcade', + 'aurora', + 'avalon', + 'baby', + 'baggage', + 'balloon', + 'bank', + 'basil', + 'begin', + 'biscuit', + 'blue', + 'bombay', + 'botanic', + 'brain', + 'brenda', + 'brigade', + 'cable', + 'calibre', + 'carmen', + 'cello', + 'celtic', + 'chariot', + 'chrome', + 'citrus', + 'civil', + 'cloud', + 'combine', + 'common', + 'cool', + 'copper', + 'coral', + 'crater', + 'cubic', + 'cupid', + 'cycle', + 'depend', + 'door', + 'dream', + 'dynasty', + 'edison', + 'edition', + 'enigma', + 'equal', + 'eric', + 'event', + 'evita', + 'exodus', + 'extend', + 'famous', + 'farmer', + 'food', + 'fossil', + 'frog', + 'fruit', + 'geneva', + 'gentle', + 'george', + 'giant', + 'gilbert', + 'gossip', + 'gram', + 'greek', + 'grille', + 'hammer', + 'harvest', + 'hazard', + 'heaven', + 'herbert', + 'heroic', + 'hexagon', + 'husband', + 'immune', + 'inca', + 'inch', + 'initial', + 'isabel', + 'ivory', + 'jason', + 'jerome', + 'joel', + 'joshua', + 'journal', + 'judge', + 'juliet', + 'jump', + 'justice', + 'kimono', + 'kinetic', + 'leonid', + 'leopard', + 'lima', + 'maze', + 'medusa', + 'member', + 'memphis', + 'michael', + 'miguel', + 'milan', + 'mile', + 'miller', + 'mimic', + 'mimosa', + 'mission', + 'monkey', + 'moral', + 'moses', + 'mouse', + 'nancy', + 'natasha', + 'nebula', + 'nickel', + 'nina', + 'noise', + 'orchid', + 'oregano', + 'origami', + 'orinoco', + 'orion', + 'othello', + 'paper', + 'paprika', + 'prelude', + 'prepare', + 'pretend', + 'promise', + 'prosper', + 'provide', + 'puzzle', + 'remote', + 'repair', + 'reply', + 'rival', + 'riviera', + 'robin', + 'rose', + 'rover', + 'rudolf', + 'saga', + 'sahara', + 'scholar', + 'shelter', + 'ship', + 'shoe', + 'sigma', + 'sister', + 'sleep', + 'smile', + 'spain', + 'spark', + 'split', + 'spray', + 'square', + 'stadium', + 'star', + 'storm', + 'story', + 'strange', + 'stretch', + 'stuart', + 'subway', + 'sugar', + 'sulfur', + 'summer', + 'survive', + 'sweet', + 'swim', + 'table', + 'taboo', + 'target', + 'teacher', + 'telecom', + 'temple', + 'tibet', + 'ticket', + 'tina', + 'today', + 'toga', + 'tommy', + 'tower', + 'trivial', + 'tunnel', + 'turtle', + 'twin', + 'uncle', + 'unicorn', + 'unique', + 'update', + 'valery', + 'vega', + 'version', + 'voodoo', + 'warning', + 'william', + 'wonder', + 'year', + 'yellow', + 'young', + 'absent', + 'absorb', + 'absurd', + 'accent', + 'alfonso', + 'alias', + 'ambient', + 'anagram', + 'andy', + 'anvil', + 'appear', + 'apropos', + 'archer', + 'ariel', + 'armor', + 'arrow', + 'austin', + 'avatar', + 'axis', + 'baboon', + 'bahama', + 'bali', + 'balsa', + 'barcode', + 'bazooka', + 'beach', + 'beast', + 'beatles', + 'beauty', + 'before', + 'benny', + 'betty', + 'between', + 'beyond', + 'billy', + 'bison', + 'blast', + 'bless', + 'bogart', + 'bonanza', + 'book', + 'border', + 'brave', + 'bread', + 'break', + 'broken', + 'bucket', + 'buenos', + 'buffalo', + 'bundle', + 'button', + 'buzzer', + 'byte', + 'caesar', + 'camilla', + 'canary', + 'candid', + 'carrot', + 'cave', + 'chant', + 'child', + 'choice', + 'chris', + 'cipher', + 'clarion', + 'clark', + 'clever', + 'cliff', + 'clone', + 'conan', + 'conduct', + 'congo', + 'costume', + 'cotton', + 'cover', + 'crack', + 'current', + 'danube', + 'data', + 'decide', + 'deposit', + 'desire', + 'detail', + 'dexter', + 'dinner', + 'donor', + 'druid', + 'drum', + 'easy', + 'eddie', + 'enjoy', + 'enrico', + 'epoxy', + 'erosion', + 'except', + 'exile', + 'explain', + 'fame', + 'fast', + 'father', + 'felix', + 'field', + 'fiona', + 'fire', + 'fish', + 'flame', + 'flex', + 'flipper', + 'float', + 'flood', + 'floor', + 'forbid', + 'forever', + 'fractal', + 'frame', + 'freddie', + 'front', + 'fuel', + 'gallop', + 'game', + 'garbo', + 'gate', + 'gelatin', + 'gibson', + 'ginger', + 'giraffe', + 'gizmo', + 'glass', + 'goblin', + 'gopher', + 'grace', + 'gray', + 'gregory', + 'grid', + 'griffin', + 'ground', + 'guest', + 'gustav', + 'gyro', + 'hair', + 'halt', + 'harris', + 'heart', + 'heavy', + 'herman', + 'hippie', + 'hobby', + 'honey', + 'hope', + 'horse', + 'hostel', + 'hydro', + 'imitate', + 'info', + 'ingrid', + 'inside', + 'invent', + 'invest', + 'invite', + 'ivan', + 'james', + 'jester', + 'jimmy', + 'join', + 'joseph', + 'juice', + 'julius', + 'july', + 'kansas', + 'karl', + 'kevin', + 'kiwi', + 'ladder', + 'lake', + 'laura', + 'learn', + 'legacy', + 'legend', + 'lesson', + 'life', + 'light', + 'list', + 'locate', + 'lopez', + 'lorenzo', + 'love', + 'lunch', + 'malta', + 'mammal', + 'margin', + 'margo', + 'marion', + 'mask', + 'match', + 'mayday', + 'meaning', + 'mercy', + 'middle', + 'mike', + 'mirror', + 'modest', + 'morph', + 'morris', + 'mystic', + 'nadia', + 'nato', + 'navy', + 'needle', + 'neuron', + 'never', + 'newton', + 'nice', + 'night', + 'nissan', + 'nitro', + 'nixon', + 'north', + 'oberon', + 'octavia', + 'ohio', + 'olga', + 'open', + 'opus', + 'orca', + 'oval', + 'owner', + 'page', + 'paint', + 'palma', + 'parent', + 'parlor', + 'parole', + 'paul', + 'peace', + 'pearl', + 'perform', + 'phoenix', + 'phrase', + 'pierre', + 'pinball', + 'place', + 'plate', + 'plato', + 'plume', + 'pogo', + 'point', + 'polka', + 'poncho', + 'powder', + 'prague', + 'press', + 'presto', + 'pretty', + 'prime', + 'promo', + 'quest', + 'quick', + 'quiz', + 'quota', + 'race', + 'rachel', + 'raja', + 'ranger', + 'region', + 'remark', + 'rent', + 'reward', + 'rhino', + 'ribbon', + 'rider', + 'road', + 'rodent', + 'round', + 'rubber', + 'ruby', + 'rufus', + 'sabine', + 'saddle', + 'sailor', + 'saint', + 'salt', + 'scale', + 'scuba', + 'season', + 'secure', + 'shake', + 'shallow', + 'shannon', + 'shave', + 'shelf', + 'sherman', + 'shine', + 'shirt', + 'side', + 'sinatra', + 'sincere', + 'size', + 'slalom', + 'slow', + 'small', + 'snow', + 'sofia', + 'song', + 'sound', + 'south', + 'speech', + 'spell', + 'spend', + 'spoon', + 'stage', + 'stamp', + 'stand', + 'state', + 'stella', + 'stick', + 'sting', + 'stock', + 'store', + 'sunday', + 'sunset', + 'support', + 'supreme', + 'sweden', + 'swing', + 'tape', + 'tavern', + 'think', + 'thomas', + 'tictac', + 'time', + 'toast', + 'tobacco', + 'tonight', + 'torch', + 'torso', + 'touch', + 'toyota', + 'trade', + 'tribune', + 'trinity', + 'triton', + 'truck', + 'trust', + 'type', + 'under', + 'unit', + 'urban', + 'urgent', + 'user', + 'value', + 'vendor', + 'venice', + 'verona', + 'vibrate', + 'virgo', + 'visible', + 'vista', + 'vital', + 'voice', + 'vortex', + 'waiter', + 'watch', + 'wave', + 'weather', + 'wedding', + 'wheel', + 'whiskey', + 'wisdom', + 'android', + 'annex', + 'armani', + 'cake', + 'confide', + 'deal', + 'define', + 'dispute', + 'genuine', + 'idiom', + 'impress', + 'include', + 'ironic', + 'null', + 'nurse', + 'obscure', + 'prefer', + 'prodigy', + 'ego', + 'fax', + 'jet', + 'job', + 'rio', + 'ski', + 'yes', +]; diff --git a/app/e2e/server/index.js b/app/e2e/server/index.js index 767b153c4bc3..ad982fd196a9 100644 --- a/app/e2e/server/index.js +++ b/app/e2e/server/index.js @@ -1,5 +1,5 @@ import { callbacks } from '../../../lib/callbacks'; -import { Notifications } from '../../notifications'; +import { api } from '../../../server/sdk/api'; import './settings'; import './beforeCreateRoom'; @@ -14,7 +14,7 @@ import './methods/requestSubscriptionKeys'; callbacks.add( 'afterJoinRoom', (user, room) => { - Notifications.notifyRoom('e2e.keyRequest', room._id, room.e2eKeyId); + api.broadcast('notify.e2e.keyRequest', room._id, room.e2eKeyId); }, callbacks.priority.MEDIUM, 'e2e', diff --git a/app/e2e/server/methods/getUsersOfRoomWithoutKey.js b/app/e2e/server/methods/getUsersOfRoomWithoutKey.js index f7ae884b74b1..87984231cc81 100644 --- a/app/e2e/server/methods/getUsersOfRoomWithoutKey.js +++ b/app/e2e/server/methods/getUsersOfRoomWithoutKey.js @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; -import { canAccessRoom } from '../../../authorization/server'; +import { canAccessRoomId } from '../../../authorization/server'; import { Subscriptions, Users } from '../../../models/server'; Meteor.methods({ @@ -21,10 +21,8 @@ Meteor.methods({ }); } - if (!canAccessRoom({ _id: rid }, { _id: userId })) { - throw new Meteor.Error('error-invalid-room', 'Invalid room', { - method: 'e2e.getUsersOfRoomWithoutKey', - }); + if (!canAccessRoomId(rid, userId)) { + throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'e2e.getUsersOfRoomWithoutKey' }); } const subscriptions = Subscriptions.findByRidWithoutE2EKey(rid, { diff --git a/app/e2e/server/methods/requestSubscriptionKeys.js b/app/e2e/server/methods/requestSubscriptionKeys.js index 2a381c2eea88..76d28b27c599 100644 --- a/app/e2e/server/methods/requestSubscriptionKeys.js +++ b/app/e2e/server/methods/requestSubscriptionKeys.js @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; -import { Subscriptions, Rooms } from '../../../models'; -import { Notifications } from '../../../notifications'; +import { Subscriptions, Rooms } from '../../../models/server'; +import { api } from '../../../../server/sdk/api'; Meteor.methods({ 'e2e.requestSubscriptionKeys'() { @@ -27,7 +27,7 @@ Meteor.methods({ const rooms = Rooms.find(query); rooms.forEach((room) => { - Notifications.notifyRoom('e2e.keyRequest', room._id, room.e2eKeyId); + api.broadcast('notify.e2e.keyRequest', room._id, room.e2eKeyId); }); return true; diff --git a/app/e2e/server/methods/setRoomKeyID.js b/app/e2e/server/methods/setRoomKeyID.js index 2f69e0be3717..bae5da004019 100644 --- a/app/e2e/server/methods/setRoomKeyID.js +++ b/app/e2e/server/methods/setRoomKeyID.js @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; -import { canAccessRoom } from '../../../authorization/server'; +import { canAccessRoomId } from '../../../authorization/server'; import { Rooms } from '../../../models/server'; Meteor.methods({ @@ -18,7 +18,7 @@ Meteor.methods({ throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'e2e.setRoomKeyID' }); } - if (!canAccessRoom({ _id: rid }, { _id: userId })) { + if (!canAccessRoomId(rid, userId)) { throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'e2e.setRoomKeyID' }); } diff --git a/app/federation/server/endpoints/dispatch.js b/app/federation/server/endpoints/dispatch.js index ece587769b76..3c841ca577a6 100644 --- a/app/federation/server/endpoints/dispatch.js +++ b/app/federation/server/endpoints/dispatch.js @@ -7,7 +7,7 @@ import { FederationRoomEvents, Messages, Rooms, Subscriptions, Users } from '../ import { FederationServers } from '../../../models/server/raw'; import { normalizers } from '../normalizers'; import { deleteRoom } from '../../../lib/server/functions'; -import { Notifications } from '../../../notifications/server'; +import { api } from '../../../../server/sdk/api'; import { FileUpload } from '../../../file-upload'; import { getFederationDomain } from '../lib/getFederationDomain'; import { decryptIfNeeded } from '../lib/crypt'; @@ -327,7 +327,7 @@ const eventHandlers = { Messages.removeById(messageId); // Notify the room - Notifications.notifyRoom(roomId, 'deleteMessage', { _id: messageId }); + api.broadcast('notify.deleteMessage', roomId, { _id: messageId }); } return eventResult; diff --git a/app/federation/server/methods/loadContextEvents.js b/app/federation/server/methods/loadContextEvents.js index 914564a61ec7..36df66a5b8f9 100644 --- a/app/federation/server/methods/loadContextEvents.js +++ b/app/federation/server/methods/loadContextEvents.js @@ -1,6 +1,6 @@ import { Meteor } from 'meteor/meteor'; -import { hasRole } from '../../../authorization/server'; +import { hasPermission } from '../../../authorization/server'; import { FederationRoomEvents } from '../../../models/server'; Meteor.methods({ @@ -9,7 +9,7 @@ Meteor.methods({ throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'loadContextEvents' }); } - if (!hasRole(Meteor.userId(), 'admin')) { + if (!hasPermission(Meteor.userId(), 'view-federation-data')) { throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'loadContextEvents', }); diff --git a/app/file-upload/server/lib/FileUpload.js b/app/file-upload/server/lib/FileUpload.js index 3fe104558e7c..5bba041a6f2d 100644 --- a/app/file-upload/server/lib/FileUpload.js +++ b/app/file-upload/server/lib/FileUpload.js @@ -19,7 +19,6 @@ import Users from '../../../models/server/models/Users'; import Rooms from '../../../models/server/models/Rooms'; import Settings from '../../../models/server/models/Settings'; import { mime } from '../../../utils/lib/mimeTypes'; -import { roomTypes } from '../../../utils/server/lib/roomTypes'; import { hasPermission } from '../../../authorization/server/functions/hasPermission'; import { canAccessRoom } from '../../../authorization/server/functions/canAccessRoom'; import { fileUploadIsValidContentType } from '../../../utils/lib/fileUploadRestrictions'; @@ -28,6 +27,7 @@ import { Messages } from '../../../models/server'; import { AppEvents, Apps } from '../../../apps/server'; import { streamToBuffer } from './streamToBuffer'; import { SystemLogger } from '../../../../server/lib/logger/system'; +import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; const cookie = new Cookies(); let maxFileSize = 0; @@ -229,7 +229,9 @@ export const FileUpload = { const future = new Future(); const s = sharp(tempFilePath); - s.rotate(); + if (settings.get('FileUpload_RotateImages') === true) { + s.rotate(); + } s.metadata( Meteor.bindEnvironment((err, metadata) => { @@ -442,7 +444,8 @@ export const FileUpload = { const isAuthorizedByCookies = rc_uid && rc_token && Users.findOneByIdAndLoginToken(rc_uid, rc_token); const isAuthorizedByHeaders = headers['x-user-id'] && headers['x-auth-token'] && Users.findOneByIdAndLoginToken(headers['x-user-id'], headers['x-auth-token']); - const isAuthorizedByRoom = rc_room_type && roomTypes.getConfig(rc_room_type).canAccessUploadedFile({ rc_uid, rc_rid, rc_token }); + const isAuthorizedByRoom = + rc_room_type && roomCoordinator.getRoomDirectives(rc_room_type)?.canAccessUploadedFile({ rc_uid, rc_rid, rc_token }); const isAuthorizedByJWT = settings.get('FileUpload_Enable_json_web_token_for_files') && token && diff --git a/app/file-upload/server/startup/settings.ts b/app/file-upload/server/startup/settings.ts index 9c0ee808de85..f55887f25993 100644 --- a/app/file-upload/server/startup/settings.ts +++ b/app/file-upload/server/startup/settings.ts @@ -32,6 +32,7 @@ settingsRegistry.addGroup('FileUpload', function () { this.add('FileUpload_RotateImages', true, { type: 'boolean', + public: true, }); this.add('FileUpload_Enable_json_web_token_for_files', true, { diff --git a/app/github-enterprise/lib/common.js b/app/github-enterprise/lib/common.js index 5cb6ef471cfe..b1b07abc6cb8 100644 --- a/app/github-enterprise/lib/common.js +++ b/app/github-enterprise/lib/common.js @@ -22,7 +22,7 @@ const GitHubEnterprise = new CustomOAuth('github_enterprise', config); if (Meteor.isServer) { Meteor.startup(function () { - settings.get('API_GitHub_Enterprise_URL', function (key, value) { + settings.watch('API_GitHub_Enterprise_URL', function (value) { config.serverURL = value; GitHubEnterprise.configure(config); }); diff --git a/app/gitlab/lib/common.js b/app/gitlab/lib/common.js index fd4520a7337d..5e3bd9941846 100644 --- a/app/gitlab/lib/common.js +++ b/app/gitlab/lib/common.js @@ -28,9 +28,7 @@ if (Meteor.isServer) { Gitlab.configure(config); }, 300); - settings.get('API_Gitlab_URL', updateConfig); - settings.get('Accounts_OAuth_Gitlab_identity_path', updateConfig); - settings.get('Accounts_OAuth_Gitlab_merge_users', updateConfig); + settings.watchMultiple(['API_Gitlab_URL', 'Accounts_OAuth_Gitlab_identity_path', 'Accounts_OAuth_Gitlab_merge_users'], updateConfig); }); } else { Meteor.startup(function () { diff --git a/app/importer-slack/server/importer.js b/app/importer-slack/server/importer.js index e18c0bb696af..e2270576fcc5 100644 --- a/app/importer-slack/server/importer.js +++ b/app/importer-slack/server/importer.js @@ -287,7 +287,7 @@ export class SlackImporter extends Base { parseMentions(newMessage) { const mentionsParser = new MentionsParser({ - pattern: () => settings.get('UTF8_User_Names_Validation'), + pattern: () => '[0-9a-zA-Z]+', useRealName: () => settings.get('UI_Use_Real_Name'), me: () => 'me', }); @@ -534,6 +534,9 @@ export class SlackImporter extends Base { } _replaceSlackUserIds(members) { + if (!members?.length) { + return []; + } return members.map((userId) => this._replaceSlackUserId(userId)); } diff --git a/app/importer/server/classes/ImportDataConverter.ts b/app/importer/server/classes/ImportDataConverter.ts index c5f1c77d940b..5cfee061b8c2 100644 --- a/app/importer/server/classes/ImportDataConverter.ts +++ b/app/importer/server/classes/ImportDataConverter.ts @@ -1,6 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { Accounts } from 'meteor/accounts-base'; import _ from 'underscore'; +import { ObjectId } from 'mongodb'; import { ImportData as ImportDataRaw } from '../../../models/server/raw'; import { IImportUser } from '../../../../definition/IImportUser'; @@ -120,6 +121,7 @@ export class ImportDataConverter { protected addObject(type: string, data: Record, options: Record = {}): void { ImportData.model.rawCollection().insert({ + _id: new ObjectId().toHexString(), data, dataType: type, ...options, @@ -257,11 +259,11 @@ export class ImportDataConverter { } if (userData.name || userData.username) { - saveUserIdentity({ _id, name: userData.name, username: userData.username }); + saveUserIdentity({ _id, name: userData.name, username: userData.username } as Parameters[0]); } if (userData.importIds.length) { - this.addUserToCache(userData.importIds[0], existingUser._id, existingUser.username); + this.addUserToCache(userData.importIds[0], existingUser._id, existingUser.username || userData.username); } } @@ -306,7 +308,7 @@ export class ImportDataConverter { } public convertUsers({ beforeImportFn, afterImportFn }: IConversionCallbacks = {}): void { - const users = Promise.await(this.getUsersToImport()); + const users = Promise.await(this.getUsersToImport()) as IImportUserRecord[]; users.forEach(({ data, _id }) => { try { if (beforeImportFn && !beforeImportFn(data, 'user')) { @@ -314,7 +316,7 @@ export class ImportDataConverter { return; } - data.emails = data.emails.filter((item) => item); + const emails = data.emails.filter(Boolean).map((email) => ({ address: email })); data.importIds = data.importIds.filter((item) => item); if (!data.emails.length && !data.username) { @@ -330,7 +332,7 @@ export class ImportDataConverter { if (!data.username) { data.username = generateUsernameSuggestion({ name: data.name, - emails: data.emails, + emails, }); } @@ -347,10 +349,11 @@ export class ImportDataConverter { } // Deleted users are 'inactive' users in Rocket.Chat + // TODO: Check data._id if exists/required or not if (data.deleted && existingUser?.active) { - setUserActiveStatus(data._id, false, true); + data._id && setUserActiveStatus(data._id, false, true); } else if (data.deleted === false && existingUser?.active === false) { - setUserActiveStatus(data._id, true); + data._id && setUserActiveStatus(data._id, true); } if (afterImportFn) { @@ -461,9 +464,12 @@ export class ImportDataConverter { const data = this.findImportedUser(importId); if (!data) { - throw new Error('importer-message-mentioned-user-not-found'); + this._logger.warn(`Mentioned user not found: ${importId}`); + continue; } + if (!data.username) { + this._logger.debug(importId); throw new Error('importer-message-mentioned-username-not-found'); } @@ -478,6 +484,31 @@ export class ImportDataConverter { return result; } + getMentionedChannelData(importId: string): IMentionedChannel | undefined { + // loading the name will also store the id on the cache if it's missing, so this won't run two queries + const name = this.findImportedRoomName(importId); + const _id = this.findImportedRoomId(importId); + + if (name && _id) { + return { + name, + _id, + }; + } + + // If the importId was not found, check if we have a room with that name + const room = Rooms.findOneByNonValidatedName(importId, { fields: { name: 1 } }); + if (room) { + this.addRoomToCache(importId, room._id); + this.addRoomNameToCache(importId, room.name); + + return { + name: room.name, + _id: room._id, + }; + } + } + convertMessageChannels(message: IImportMessage): Array | undefined { const { channels } = message; if (!channels) { @@ -486,9 +517,7 @@ export class ImportDataConverter { const result: Array = []; for (const importId of channels) { - // loading the name will also store the id on the cache if it's missing, so this won't run two queries - const name = this.findImportedRoomName(importId); - const _id = this.findImportedRoomId(importId); + const { name, _id } = this.getMentionedChannelData(importId) || {}; if (!_id || !name) { this._logger.warn(`Mentioned room not found: ${importId}`); @@ -513,24 +542,24 @@ export class ImportDataConverter { convertMessages({ beforeImportFn, afterImportFn }: IConversionCallbacks = {}): void { const rids: Array = []; const messages = Promise.await(this.getMessagesToImport()); - messages.forEach(({ data: m, _id }: IImportMessageRecord) => { + messages.forEach(({ data, _id }: IImportMessageRecord) => { try { - if (beforeImportFn && !beforeImportFn(m, 'message')) { + if (beforeImportFn && !beforeImportFn(data, 'message')) { this.skipRecord(_id); return; } - if (!m.ts || isNaN(m.ts as unknown as number)) { + if (!data.ts || isNaN(data.ts as unknown as number)) { throw new Error('importer-message-invalid-timestamp'); } - const creator = this.findImportedUser(m.u._id); + const creator = this.findImportedUser(data.u._id); if (!creator) { - this._logger.warn(`Imported user not found: ${m.u._id}`); + this._logger.warn(`Imported user not found: ${data.u._id}`); throw new Error('importer-message-unknown-user'); } - const rid = this.findImportedRoomId(m.rid); + const rid = this.findImportedRoomId(data.rid); if (!rid) { throw new Error('importer-message-unknown-room'); } @@ -539,8 +568,8 @@ export class ImportDataConverter { } // Convert the mentions and channels first because these conversions can also modify the msg in the message object - const mentions = m.mentions && this.convertMessageMentions(m); - const channels = m.channels && this.convertMessageChannels(m); + const mentions = data.mentions && this.convertMessageMentions(data); + const channels = data.channels && this.convertMessageChannels(data); const msgObj: IMessage = { rid, @@ -548,32 +577,32 @@ export class ImportDataConverter { _id: creator._id, username: creator.username, }, - msg: m.msg, - ts: m.ts, - t: m.t || undefined, - groupable: m.groupable, - tmid: m.tmid, - tlm: m.tlm, - tcount: m.tcount, - replies: m.replies && this.convertMessageReplies(m.replies), - editedAt: m.editedAt, - editedBy: m.editedBy && (this.findImportedUser(m.editedBy) || undefined), + msg: data.msg, + ts: data.ts, + t: data.t || undefined, + groupable: data.groupable, + tmid: data.tmid, + tlm: data.tlm, + tcount: data.tcount, + replies: data.replies && this.convertMessageReplies(data.replies), + editedAt: data.editedAt, + editedBy: data.editedBy && (this.findImportedUser(data.editedBy) || undefined), mentions, channels, - _importFile: m._importFile, - url: m.url, - attachments: m.attachments, - bot: m.bot, - emoji: m.emoji, - alias: m.alias, + _importFile: data._importFile, + url: data.url, + attachments: data.attachments, + bot: data.bot, + emoji: data.emoji, + alias: data.alias, }; - if (m._id) { - msgObj._id = m._id; + if (data._id) { + msgObj._id = data._id; } - if (m.reactions) { - msgObj.reactions = this.convertMessageReactions(m.reactions); + if (data.reactions) { + msgObj.reactions = this.convertMessageReactions(data.reactions); } try { @@ -584,7 +613,7 @@ export class ImportDataConverter { } if (afterImportFn) { - afterImportFn(m, 'message', true); + afterImportFn(data, 'message', true); } } catch (e) { this.saveError(_id, e); @@ -863,38 +892,38 @@ export class ImportDataConverter { convertChannels(startedByUserId: string, { beforeImportFn, afterImportFn }: IConversionCallbacks = {}): void { const channels = Promise.await(this.getChannelsToImport()); - channels.forEach(({ data: c, _id }: IImportChannelRecord) => { + channels.forEach(({ data, _id }: IImportChannelRecord) => { try { - if (beforeImportFn && !beforeImportFn(c, 'channel')) { + if (beforeImportFn && !beforeImportFn(data, 'channel')) { this.skipRecord(_id); return; } - if (!c.name && c.t !== 'd') { + if (!data.name && data.t !== 'd') { throw new Error('importer-channel-missing-name'); } - c.importIds = c.importIds.filter((item) => item); - c.users = _.uniq(c.users); + data.importIds = data.importIds.filter((item) => item); + data.users = _.uniq(data.users); - if (!c.importIds.length) { + if (!data.importIds.length) { throw new Error('importer-channel-missing-import-id'); } - const existingRoom = this.findExistingRoom(c); + const existingRoom = this.findExistingRoom(data); if (existingRoom) { - this.updateRoom(existingRoom, c, startedByUserId); + this.updateRoom(existingRoom, data, startedByUserId); } else { - this.insertRoom(c, startedByUserId); + this.insertRoom(data, startedByUserId); } - if (c.archived && c._id) { - this.archiveRoomById(c._id); + if (data.archived && data._id) { + this.archiveRoomById(data._id); } if (afterImportFn) { - afterImportFn(c, 'channel', !existingRoom); + afterImportFn(data, 'channel', !existingRoom); } } catch (e) { this.saveError(_id, e); diff --git a/app/importer/server/methods/getLatestImportOperations.js b/app/importer/server/methods/getLatestImportOperations.js index c49d05e0d98e..28154a86c7d5 100644 --- a/app/importer/server/methods/getLatestImportOperations.js +++ b/app/importer/server/methods/getLatestImportOperations.js @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { Imports } from '../../../models/server'; -import { hasRole } from '../../../authorization/server'; +import { hasPermission } from '../../../authorization/server'; Meteor.methods({ getLatestImportOperations() { @@ -13,7 +13,7 @@ Meteor.methods({ }); } - if (!hasRole(userId, 'admin')) { + if (!hasPermission(userId, 'view-import-operations')) { throw new Meteor.Error('not_authorized', 'User not authorized', { method: 'getLatestImportOperations', }); diff --git a/app/integrations/server/lib/triggerHandler.js b/app/integrations/server/lib/triggerHandler.js index 8ba50fca9bd4..17b9cbdf5c26 100644 --- a/app/integrations/server/lib/triggerHandler.js +++ b/app/integrations/server/lib/triggerHandler.js @@ -2,7 +2,6 @@ import vm from 'vm'; import { Meteor } from 'meteor/meteor'; import { Random } from 'meteor/random'; -import { fetch } from 'meteor/fetch'; import { HTTP } from 'meteor/http'; import _ from 'underscore'; import s from 'underscore.string'; @@ -16,7 +15,7 @@ import { settings } from '../../../settings/server'; import { getRoomByNameOrIdWithOptionToJoin, processWebhookMessage } from '../../../lib/server'; import { outgoingLogger } from '../logger'; import { integrations } from '../../lib/rocketchat'; -import { getUnsafeAgent } from '../../../../server/lib/getUnsafeAgent'; +import { fetch } from '../../../../server/lib/http/fetch'; export class RocketChatIntegrationHandler { constructor() { @@ -771,14 +770,15 @@ export class RocketChatIntegrationHandler { opts.headers['Content-Type'] = 'application/json'; } - fetch(opts.url, { - method: opts.method, - headers: opts.headers, - ...(settings.get('Allow_Invalid_SelfSigned_Certs') && { - agent: getUnsafeAgent(opts.url.startsWith('https:') ? 'https:' : 'http:'), - }), - ...(opts.data && { body: JSON.stringify(opts.data) }), - }) + fetch( + opts.url, + { + method: opts.method, + headers: opts.headers, + ...(opts.data && { body: JSON.stringify(opts.data) }), + }, + settings.get('Allow_Invalid_SelfSigned_Certs'), + ) .then(async (res) => { const content = await res.text(); if (!content) { diff --git a/app/integrations/server/methods/clearIntegrationHistory.ts b/app/integrations/server/methods/clearIntegrationHistory.ts index 9cdf42e41307..499c8b806ac3 100644 --- a/app/integrations/server/methods/clearIntegrationHistory.ts +++ b/app/integrations/server/methods/clearIntegrationHistory.ts @@ -8,12 +8,15 @@ Meteor.methods({ async clearIntegrationHistory(integrationId) { let integration; - if (hasPermission(this.userId, 'manage-outgoing-integrations') || hasPermission(this.userId, 'manage-outgoing-integrations', 'bot')) { + if (!this.userId) { + throw new Meteor.Error('not_authorized', 'Unauthorized', { + method: 'clearIntegrationHistory', + }); + } + + if (hasPermission(this.userId, 'manage-outgoing-integrations')) { integration = await Integrations.findOneById(integrationId); - } else if ( - hasPermission(this.userId, 'manage-own-outgoing-integrations') || - hasPermission(this.userId, 'manage-own-outgoing-integrations', 'bot') - ) { + } else if (hasPermission(this.userId, 'manage-own-outgoing-integrations')) { integration = await Integrations.findOne({ '_id': integrationId, '_createdBy._id': this.userId, diff --git a/app/integrations/server/methods/incoming/addIncomingIntegration.js b/app/integrations/server/methods/incoming/addIncomingIntegration.js index 555acbc3a459..478c4cb99314 100644 --- a/app/integrations/server/methods/incoming/addIncomingIntegration.js +++ b/app/integrations/server/methods/incoming/addIncomingIntegration.js @@ -109,7 +109,7 @@ Meteor.methods({ integration._createdAt = new Date(); integration._createdBy = Users.findOne(this.userId, { fields: { username: 1 } }); - await Roles.addUserRoles(user._id, 'bot'); + await Roles.addUserRoles(user._id, ['bot']); const result = await Integrations.insertOne(integration); diff --git a/app/integrations/server/methods/incoming/deleteIncomingIntegration.ts b/app/integrations/server/methods/incoming/deleteIncomingIntegration.ts index 3c54198dc4aa..81b1e719e40b 100644 --- a/app/integrations/server/methods/incoming/deleteIncomingIntegration.ts +++ b/app/integrations/server/methods/incoming/deleteIncomingIntegration.ts @@ -6,13 +6,14 @@ import { Integrations } from '../../../../models/server/raw'; Meteor.methods({ async deleteIncomingIntegration(integrationId) { let integration; + const { userId } = this; - if (hasPermission(this.userId, 'manage-incoming-integrations')) { + if (userId && hasPermission(userId, 'manage-incoming-integrations')) { integration = Integrations.findOneById(integrationId); - } else if (hasPermission(this.userId, 'manage-own-incoming-integrations')) { + } else if (userId && hasPermission(userId, 'manage-own-incoming-integrations')) { integration = Integrations.findOne({ '_id': integrationId, - '_createdBy._id': this.userId, + '_createdBy._id': userId, }); } else { throw new Meteor.Error('not_authorized', 'Unauthorized', { diff --git a/app/integrations/server/methods/incoming/updateIncomingIntegration.js b/app/integrations/server/methods/incoming/updateIncomingIntegration.js index 79cbb6240091..49ea3faf54ce 100644 --- a/app/integrations/server/methods/incoming/updateIncomingIntegration.js +++ b/app/integrations/server/methods/incoming/updateIncomingIntegration.js @@ -121,7 +121,7 @@ Meteor.methods({ }); } - await Roles.addUserRoles(user._id, 'bot'); + await Roles.addUserRoles(user._id, ['bot']); await Integrations.updateOne( { _id: integrationId }, diff --git a/app/integrations/server/methods/outgoing/addOutgoingIntegration.js b/app/integrations/server/methods/outgoing/addOutgoingIntegration.js index 7bad154a1802..31eabe715c6c 100644 --- a/app/integrations/server/methods/outgoing/addOutgoingIntegration.js +++ b/app/integrations/server/methods/outgoing/addOutgoingIntegration.js @@ -7,12 +7,7 @@ import { integrations } from '../../../lib/rocketchat'; Meteor.methods({ async addOutgoingIntegration(integration) { - if ( - !hasPermission(this.userId, 'manage-outgoing-integrations') && - !hasPermission(this.userId, 'manage-own-outgoing-integrations') && - !hasPermission(this.userId, 'manage-outgoing-integrations', 'bot') && - !hasPermission(this.userId, 'manage-own-outgoing-integrations', 'bot') - ) { + if (!hasPermission(this.userId, 'manage-outgoing-integrations') && !hasPermission(this.userId, 'manage-own-outgoing-integrations')) { throw new Meteor.Error('not_authorized'); } diff --git a/app/integrations/server/methods/outgoing/deleteOutgoingIntegration.ts b/app/integrations/server/methods/outgoing/deleteOutgoingIntegration.ts index d7ce4c335569..6926e18b1c7c 100644 --- a/app/integrations/server/methods/outgoing/deleteOutgoingIntegration.ts +++ b/app/integrations/server/methods/outgoing/deleteOutgoingIntegration.ts @@ -7,12 +7,15 @@ Meteor.methods({ async deleteOutgoingIntegration(integrationId) { let integration; - if (hasPermission(this.userId, 'manage-outgoing-integrations') || hasPermission(this.userId, 'manage-outgoing-integrations', 'bot')) { + if (!this.userId) { + throw new Meteor.Error('not_authorized', 'Unauthorized', { + method: 'deleteOutgoingIntegration', + }); + } + + if (hasPermission(this.userId, 'manage-outgoing-integrations')) { integration = Integrations.findOneById(integrationId); - } else if ( - hasPermission(this.userId, 'manage-own-outgoing-integrations') || - hasPermission(this.userId, 'manage-own-outgoing-integrations', 'bot') - ) { + } else if (hasPermission(this.userId, 'manage-own-outgoing-integrations')) { integration = Integrations.findOne({ '_id': integrationId, '_createdBy._id': this.userId, diff --git a/app/integrations/server/methods/outgoing/replayOutgoingIntegration.ts b/app/integrations/server/methods/outgoing/replayOutgoingIntegration.ts index a3f28e847546..07e47bbf4ca5 100644 --- a/app/integrations/server/methods/outgoing/replayOutgoingIntegration.ts +++ b/app/integrations/server/methods/outgoing/replayOutgoingIntegration.ts @@ -8,20 +8,19 @@ Meteor.methods({ async replayOutgoingIntegration({ integrationId, historyId }) { let integration; - if (hasPermission(this.userId, 'manage-outgoing-integrations') || hasPermission(this.userId, 'manage-outgoing-integrations', 'bot')) { + if (!this.userId) { + throw new Meteor.Error('not_authorized', 'Unauthorized', { + method: 'replayOutgoingIntegration', + }); + } + + if (hasPermission(this.userId, 'manage-outgoing-integrations')) { integration = await Integrations.findOneById(integrationId); - } else if ( - hasPermission(this.userId, 'manage-own-outgoing-integrations') || - hasPermission(this.userId, 'manage-own-outgoing-integrations', 'bot') - ) { + } else if (hasPermission(this.userId, 'manage-own-outgoing-integrations')) { integration = await Integrations.findOne({ '_id': integrationId, '_createdBy._id': this.userId, }); - } else { - throw new Meteor.Error('not_authorized', 'Unauthorized', { - method: 'replayOutgoingIntegration', - }); } if (!integration) { diff --git a/app/invites/server/functions/findOrCreateInvite.js b/app/invites/server/functions/findOrCreateInvite.js index cee51781c7dc..b50803a73c23 100644 --- a/app/invites/server/functions/findOrCreateInvite.js +++ b/app/invites/server/functions/findOrCreateInvite.js @@ -1,13 +1,14 @@ import { Meteor } from 'meteor/meteor'; import { Random } from 'meteor/random'; -import { hasPermission } from '../../../authorization'; -import { Notifications } from '../../../notifications'; +import { hasPermission } from '../../../authorization/server'; +import { api } from '../../../../server/sdk/api'; import { Subscriptions, Rooms } from '../../../models/server'; import { Invites } from '../../../models/server/raw'; -import { settings } from '../../../settings'; +import { settings } from '../../../settings/server'; import { getURL } from '../../../utils/lib/getURL'; -import { roomTypes, RoomMemberActions } from '../../../utils/server'; +import { RoomMemberActions } from '../../../../definition/IRoomTypeConfig'; +import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; function getInviteUrl(invite) { const { _id } = invite; @@ -51,7 +52,7 @@ export const findOrCreateInvite = async (userId, invite) => { } const room = Rooms.findOneById(invite.rid); - if (!roomTypes.getConfig(room.t).allowMemberAction(room, RoomMemberActions.INVITE)) { + if (!roomCoordinator.getRoomDirectives(room.t)?.allowMemberAction(room, RoomMemberActions.INVITE)) { throw new Meteor.Error('error-room-type-not-allowed', 'Cannot create invite links for this room type', { method: 'findOrCreateInvite', }); @@ -98,7 +99,8 @@ export const findOrCreateInvite = async (userId, invite) => { }; await Invites.insertOne(createInvite); - Notifications.notifyUser(userId, 'updateInvites', { invite: createInvite }); + + api.broadcast('notify.updateInvites', userId, { invite: createInvite }); createInvite.url = getInviteUrl(createInvite); return createInvite; diff --git a/app/invites/server/functions/useInviteToken.js b/app/invites/server/functions/useInviteToken.js index 3d43fd7e88b1..4f98c470475a 100644 --- a/app/invites/server/functions/useInviteToken.js +++ b/app/invites/server/functions/useInviteToken.js @@ -4,7 +4,8 @@ import { Users, Subscriptions } from '../../../models/server'; import { Invites } from '../../../models/server/raw'; import { validateInviteToken } from './validateInviteToken'; import { addUserToRoom } from '../../../lib/server/functions/addUserToRoom'; -import { roomTypes, RoomMemberActions } from '../../../utils/server'; +import { RoomMemberActions } from '../../../../definition/IRoomTypeConfig'; +import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; export const useInviteToken = async (userId, token) => { if (!userId) { @@ -23,7 +24,7 @@ export const useInviteToken = async (userId, token) => { const { inviteData, room } = await validateInviteToken(token); - if (!roomTypes.getConfig(room.t).allowMemberAction(room, RoomMemberActions.INVITE)) { + if (!roomCoordinator.getRoomDirectives(room.t)?.allowMemberAction(room, RoomMemberActions.INVITE)) { throw new Meteor.Error('error-room-type-not-allowed', "Can't join room of this type via invite", { method: 'useInviteToken', field: 'token', diff --git a/app/invites/server/functions/validateInviteToken.js b/app/invites/server/functions/validateInviteToken.js index cc7ea9f194a5..385d55ca0ee1 100644 --- a/app/invites/server/functions/validateInviteToken.js +++ b/app/invites/server/functions/validateInviteToken.js @@ -19,9 +19,7 @@ export const validateInviteToken = async (token) => { }); } - const room = Rooms.findOneById(inviteData.rid, { - fields: { _id: 1, name: 1, fname: 1, t: 1, prid: 1 }, - }); + const room = Rooms.findOneById(inviteData.rid); if (!room) { throw new Meteor.Error('error-invalid-room', 'The invite token is invalid.', { method: 'validateInviteToken', diff --git a/app/irc/server/irc-bridge/peerHandlers/leftChannel.js b/app/irc/server/irc-bridge/peerHandlers/leftChannel.js index d9c43ca6437c..13c29e828c46 100644 --- a/app/irc/server/irc-bridge/peerHandlers/leftChannel.js +++ b/app/irc/server/irc-bridge/peerHandlers/leftChannel.js @@ -17,5 +17,5 @@ export default function handleLeftChannel(args) { } this.log(`${user.username} left room ${room.name}`); - removeUserFromRoom(room._id, user); + Promise.await(removeUserFromRoom(room._id, user)); } diff --git a/app/lib/README.md b/app/lib/README.md index 2308f1e0da4f..708a9a21790e 100644 --- a/app/lib/README.md +++ b/app/lib/README.md @@ -47,94 +47,6 @@ settingsRegistry.addGroup('Settings_Group', function() { * `enableQuery` - Only enable this setting if the correspondent setting has the value specified * `alert` - Shows an alert message with the given text -#### Custom Room Types -Custom room types are now a regular and expected customization to Rocket.Chat. As a result of this, we have expanded -the capabilities which custom room types are given. Custom room types now have full control over the settings which -display on the room's setting tab. To achieve this, however, we had to forcefully break the previous behavior in order -to force the new behavior. If, after merging, you are getting the error `Error: Invalid Room Configuration object, it must extend "RoomTypeConfig"`, -then you will need to modify your custom room code to the class setup explained below. - -Implementing the new types requires two steps. First step is to implement a your own custom class to define the route configuration. -Then the second step would be to implement your own room class, which will contain all of the required methods and configuration -for usage in the clients. - -##### First Step: RoomTypeRouteConfig -If your room adds a custom route to the browser, then you will need to create a class which extends `RoomTypeRouteConfig`. -This class can be imported using es6 style imports `import { RoomTypeRouteConfig } from 'meteor/rocketchat:lib';`. There -are two fields which are required when constructing your route which is `{ name, path }` and both are strings. Then your -class must implement the `action(params, queryParams)` method. - -```javascript -class LivechatRoomRoute extends RoomTypeRouteConfig { - constructor() { - super({ - name: 'live', - path: '/live/:code(\\d+)' - }); - } - - action(params) { - openRoom('l', params.code); - } - - link(sub) { - return { - code: sub.code - }; - } -} -``` - -##### Second Step: RoomTypeConfig -Next you need to create a class which extends `RoomTypeConfig`. This class can be imported using the es6 style import -such as `import { RoomTypeConfig } from 'meteor/rocketchat:lib';`. There are two required properties when constructing -your class which is `{ identifier, order }` with the `identifier` being a string and `order` being a number. There are -default implementations of the required methods, so unless you want to overwrite the default behavior, such as disallowing -certain settings, then you will need to implement that method and handle it. - -```javascript -class LivechatRoomType extends RoomTypeConfig { - constructor() { - super({ - identifier: 'l', - order: 5, - icon: 'livechat', - label: 'Livechat', - route: new LivechatRoomRoute() //defined above, see the example - }); - } - - roomName(roomData) { - if (!roomData.name) { - return roomData.label; - } else { - return roomData.name; - } - } - - condition() { - return RocketChat.settings.get('Livechat_enabled') && RocketChat.authz.hasPermission('view-l-room'); - } -} -``` - -Then for publishing of the data, you will need to provide the information about the new room with (on the server): - -```javascript -RocketChat.roomTypes.setPublish('l', (identifier) => { - return RocketChat.models.Rooms.findByTypeAndName('l', identifier, { - fields: { - name: 1, - t: 1, - cl: 1, - u: 1, - usernames: 1, - v: 1 - } - }); -}); -``` - ### AccountBox You can add items to the left upper corner drop menu: diff --git a/app/lib/client/index.js b/app/lib/client/index.js index 321f6f1e5bf3..b32859f3e57c 100644 --- a/app/lib/client/index.js +++ b/app/lib/client/index.js @@ -4,6 +4,5 @@ import './OAuthProxy'; import './methods/sendMessage'; import './views/customFieldsForm.html'; import './views/customFieldsForm'; -import '../startup/defaultRoomTypes'; export * from './lib'; diff --git a/app/lib/lib/MessageTypes.js b/app/lib/lib/MessageTypes.js index 19bcc537dd72..d3167b24c2a7 100644 --- a/app/lib/lib/MessageTypes.js +++ b/app/lib/lib/MessageTypes.js @@ -26,6 +26,16 @@ Meteor.startup(function () { }; }, }); + MessageTypes.registerType({ + id: 'added-user-to-team', + system: true, + message: 'Added__username__to_team', + data(message) { + return { + user_added: message.msg, + }; + }, + }); MessageTypes.registerType({ id: 'ru', system: true, @@ -37,6 +47,16 @@ Meteor.startup(function () { }; }, }); + MessageTypes.registerType({ + id: 'removed-user-from-team', + system: true, + message: 'Removed__username__from_team', + data(message) { + return { + user_removed: message.msg, + }; + }, + }); MessageTypes.registerType({ id: 'ul', system: true, @@ -57,6 +77,56 @@ Meteor.startup(function () { }; }, }); + MessageTypes.registerType({ + id: 'user-converted-to-team', + system: true, + message: 'Converted__roomName__to_team', + data(message) { + return { + roomName: message.msg, + }; + }, + }); + MessageTypes.registerType({ + id: 'user-converted-to-channel', + system: true, + message: 'Converted__roomName__to_channel', + data(message) { + return { + roomName: message.msg, + }; + }, + }); + MessageTypes.registerType({ + id: 'user-removed-room-from-team', + system: true, + message: 'Removed__roomName__from_this_team', + data(message) { + return { + roomName: message.msg, + }; + }, + }); + MessageTypes.registerType({ + id: 'user-deleted-room-from-team', + system: true, + message: 'Deleted__roomName__', + data(message) { + return { + roomName: message.msg, + }; + }, + }); + MessageTypes.registerType({ + id: 'user-added-room-to-team', + system: true, + message: 'added__roomName__to_team', + data(message) { + return { + roomName: message.msg, + }; + }, + }); MessageTypes.registerType({ id: 'uj', system: true, @@ -246,18 +316,34 @@ export const MessageTypesValues = [ key: 'uj', i18nLabel: 'Message_HideType_uj', }, + { + key: 'ujt', + i18nLabel: 'Message_HideType_ujt', + }, { key: 'ul', i18nLabel: 'Message_HideType_ul', }, + { + key: 'ult', + i18nLabel: 'Message_HideType_ult', + }, { key: 'ru', i18nLabel: 'Message_HideType_ru', }, + { + key: 'removed-user-from-team', + i18nLabel: 'Message_HideType_removed_user_from_team', + }, { key: 'au', i18nLabel: 'Message_HideType_au', }, + { + key: 'added-user-to-team', + i18nLabel: 'Message_HideType_added_user_to_team', + }, { key: 'mute_unmute', i18nLabel: 'Message_HideType_mute_unmute', @@ -287,11 +373,11 @@ export const MessageTypesValues = [ i18nLabel: 'Message_HideType_subscription_role_removed', }, { - key: 'room_archived', + key: 'room-archived', i18nLabel: 'Message_HideType_room_archived', }, { - key: 'room_unarchived', + key: 'room-unarchived', i18nLabel: 'Message_HideType_room_unarchived', }, { @@ -330,4 +416,24 @@ export const MessageTypesValues = [ key: 'room-allowed-reacting', i18nLabel: 'Message_HideType_room_allowed_reacting', }, + { + key: 'user-added-room-to-team', + i18nLabel: 'Message_HideType_user_added_room_to_team', + }, + { + key: 'user-converted-to-channel', + i18nLabel: 'Message_HideType_user_converted_to_channel', + }, + { + key: 'user-converted-to-team', + i18nLabel: 'Message_HideType_user_converted_to_team', + }, + { + key: 'user-deleted-room-from-team', + i18nLabel: 'Message_HideType_user_deleted_room_from_team', + }, + { + key: 'user-removed-room-from-team', + i18nLabel: 'Message_HideType_user_removed_room_from_team', + }, ]; diff --git a/app/lib/lib/roomTypes/conversation.js b/app/lib/lib/roomTypes/conversation.js deleted file mode 100644 index 7e1ca775bd1d..000000000000 --- a/app/lib/lib/roomTypes/conversation.js +++ /dev/null @@ -1,18 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import { getUserPreference, RoomTypeConfig } from '../../../utils'; - -export class ConversationRoomType extends RoomTypeConfig { - constructor() { - super({ - identifier: 'merged', - order: 30, - label: 'Conversations', - }); - } - - condition() { - // returns true only if sidebarGroupByType is not set - return !getUserPreference(Meteor.userId(), 'sidebarGroupByType'); - } -} diff --git a/app/lib/lib/roomTypes/direct.js b/app/lib/lib/roomTypes/direct.js deleted file mode 100644 index a26f158b0fdc..000000000000 --- a/app/lib/lib/roomTypes/direct.js +++ /dev/null @@ -1,215 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { Session } from 'meteor/session'; - -import { ChatRoom, Subscriptions } from '../../../models'; -import { openRoom } from '../../../ui-utils'; -import { getUserPreference, RoomTypeConfig, RoomTypeRouteConfig, RoomSettingsEnum, RoomMemberActions, UiTextContext } from '../../../utils'; -import { hasPermission, hasAtLeastOnePermission } from '../../../authorization'; -import { settings } from '../../../settings'; -import { getUserAvatarURL } from '../../../utils/lib/getUserAvatarURL'; -import { getAvatarURL } from '../../../utils/lib/getAvatarURL'; - -export class DirectMessageRoomRoute extends RoomTypeRouteConfig { - constructor() { - super({ - name: 'direct', - path: '/direct/:rid/:tab?/:context?', - }); - } - - action(params) { - return openRoom('d', params.rid); - } - - link(sub) { - return { rid: sub.rid || sub.name }; - } -} - -export class DirectMessageRoomType extends RoomTypeConfig { - constructor() { - super({ - identifier: 'd', - order: 50, - icon: 'at', - label: 'Direct_Messages', - route: new DirectMessageRoomRoute(), - }); - } - - getIcon(roomData) { - if (this.isGroupChat(roomData)) { - return 'balloon'; - } - return this.icon; - } - - findRoom(identifier) { - if (!hasPermission('view-d-room')) { - return null; - } - - const query = { - t: 'd', - $or: [{ name: identifier }, { rid: identifier }], - }; - - const subscription = Subscriptions.findOne(query); - if (subscription && subscription.rid) { - return ChatRoom.findOne(subscription.rid); - } - } - - roomName(roomData) { - // this function can receive different types of data - // if it doesn't have fname and name properties, should be a Room object - // so, need to find the related subscription - const subscription = roomData && (roomData.fname || roomData.name) ? roomData : Subscriptions.findOne({ rid: roomData._id }); - - if (subscription === undefined) { - return; - } - - if (settings.get('UI_Use_Real_Name') && subscription.fname) { - return subscription.fname; - } - - return subscription.name; - } - - secondaryRoomName(roomData) { - if (settings.get('UI_Use_Real_Name')) { - const subscription = Subscriptions.findOne({ rid: roomData._id }, { fields: { name: 1 } }); - return subscription && subscription.name; - } - } - - condition() { - const groupByType = getUserPreference(Meteor.userId(), 'sidebarGroupByType'); - return groupByType && hasAtLeastOnePermission(['view-d-room', 'view-joined-room']); - } - - getUserStatus(roomId) { - const subscription = Subscriptions.findOne({ rid: roomId }); - if (subscription == null) { - return; - } - - return Session.get(`user_${subscription.name}_status`); - } - - getUserStatusText(roomId) { - const subscription = Subscriptions.findOne({ rid: roomId }); - if (subscription == null) { - return; - } - - return Session.get(`user_${subscription.name}_status_text`); - } - - allowRoomSettingChange(room, setting) { - switch (setting) { - case RoomSettingsEnum.TYPE: - case RoomSettingsEnum.NAME: - case RoomSettingsEnum.SYSTEM_MESSAGES: - case RoomSettingsEnum.DESCRIPTION: - case RoomSettingsEnum.READ_ONLY: - case RoomSettingsEnum.REACT_WHEN_READ_ONLY: - case RoomSettingsEnum.ARCHIVE_OR_UNARCHIVE: - case RoomSettingsEnum.JOIN_CODE: - return false; - case RoomSettingsEnum.E2E: - return settings.get('E2E_Enable') === true; - default: - return true; - } - } - - allowMemberAction(room, action) { - switch (action) { - case RoomMemberActions.BLOCK: - return !this.isGroupChat(room); - default: - return false; - } - } - - enableMembersListProfile() { - return true; - } - - userDetailShowAll(/* room */) { - return true; - } - - getUiText(context) { - switch (context) { - case UiTextContext.HIDE_WARNING: - return 'Hide_Private_Warning'; - case UiTextContext.LEAVE_WARNING: - return 'Leave_Private_Warning'; - default: - return ''; - } - } - - /** - * Returns details to use on notifications - * - * @param {object} room - * @param {object} user - * @param {string} notificationMessage - * @return {object} Notification details - */ - getNotificationDetails(room, user, notificationMessage) { - if (!Meteor.isServer) { - return {}; - } - - if (this.isGroupChat(room)) { - return { - title: this.roomName(room), - text: `${(settings.get('UI_Use_Real_Name') && user.name) || user.username}: ${notificationMessage}`, - }; - } - - return { - title: (settings.get('UI_Use_Real_Name') && user.name) || user.username, - text: notificationMessage, - }; - } - - getAvatarPath(roomData, subData) { - if (!roomData && !subData) { - return ''; - } - - // if coming from sidenav search - if (roomData.name && roomData.avatarETag) { - return getUserAvatarURL(roomData.name, roomData.avatarETag); - } - - if (this.isGroupChat(roomData)) { - return getAvatarURL({ username: roomData.uids.length + roomData.usernames.join() }); - } - - const sub = subData || Subscriptions.findOne({ rid: roomData._id }, { fields: { name: 1 } }); - - if (sub && sub.name) { - const user = Meteor.users.findOne({ username: sub.name }, { fields: { username: 1, avatarETag: 1 } }); - return getUserAvatarURL(user?.username || sub.name, user?.avatarETag); - } - - if (roomData) { - return getUserAvatarURL(roomData.name || this.roomName(roomData)); // rooms should have no name for direct messages... - } - } - - includeInDashboard() { - return true; - } - - isGroupChat(room) { - return room && room.uids && room.uids.length > 2; - } -} diff --git a/app/lib/lib/roomTypes/favorite.js b/app/lib/lib/roomTypes/favorite.js deleted file mode 100644 index fc4e39a64e8b..000000000000 --- a/app/lib/lib/roomTypes/favorite.js +++ /dev/null @@ -1,20 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import { settings } from '../../../settings'; -import { getUserPreference, RoomTypeConfig } from '../../../utils'; - -export class FavoriteRoomType extends RoomTypeConfig { - constructor() { - super({ - identifier: 'f', - order: 20, - header: 'favorite', - icon: 'star', - label: 'Favorites', - }); - } - - condition() { - return settings.get('Favorite_Rooms') && getUserPreference(Meteor.userId(), 'sidebarShowFavorites'); - } -} diff --git a/app/lib/lib/roomTypes/index.js b/app/lib/lib/roomTypes/index.js deleted file mode 100644 index dfa3a5bb2b74..000000000000 --- a/app/lib/lib/roomTypes/index.js +++ /dev/null @@ -1,8 +0,0 @@ -import { ConversationRoomType } from './conversation'; -import { DirectMessageRoomType } from './direct'; -import { FavoriteRoomType } from './favorite'; -import { PrivateRoomType } from './private'; -import { PublicRoomType } from './public'; -import { UnreadRoomType } from './unread'; - -export { ConversationRoomType, DirectMessageRoomType, FavoriteRoomType, PrivateRoomType, PublicRoomType, UnreadRoomType }; diff --git a/app/lib/lib/roomTypes/private.js b/app/lib/lib/roomTypes/private.js deleted file mode 100644 index d4dd7e931a05..000000000000 --- a/app/lib/lib/roomTypes/private.js +++ /dev/null @@ -1,135 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import { ChatRoom, ChatSubscription } from '../../../models'; -import { openRoom } from '../../../ui-utils'; -import { settings } from '../../../settings'; -import { hasAtLeastOnePermission, hasPermission } from '../../../authorization'; -import { getUserPreference, RoomSettingsEnum, RoomTypeConfig, RoomTypeRouteConfig, UiTextContext, RoomMemberActions } from '../../../utils'; -import { getAvatarURL } from '../../../utils/lib/getAvatarURL'; - -export class PrivateRoomRoute extends RoomTypeRouteConfig { - constructor() { - super({ - name: 'group', - path: '/group/:name/:tab?/:context?', - }); - } - - action(params) { - return openRoom('p', params.name); - } -} - -export class PrivateRoomType extends RoomTypeConfig { - constructor() { - super({ - identifier: 'p', - order: 40, - icon: 'hashtag-lock', - label: 'Private_Groups', - route: new PrivateRoomRoute(), - }); - } - - getIcon(roomData) { - if (roomData.prid) { - return 'discussion'; - } - if (roomData.teamMain) { - return 'team-lock'; - } - return this.icon; - } - - findRoom(identifier) { - const query = { - t: 'p', - name: identifier, - }; - - return ChatRoom.findOne(query); - } - - roomName(roomData) { - if (roomData.prid) { - return roomData.fname; - } - if (settings.get('UI_Allow_room_names_with_special_chars')) { - return roomData.fname || roomData.name; - } - - return roomData.name; - } - - condition() { - const groupByType = getUserPreference(Meteor.userId(), 'sidebarGroupByType'); - return groupByType && hasPermission('view-p-room'); - } - - isGroupChat() { - return true; - } - - canAddUser(room) { - return hasAtLeastOnePermission(['add-user-to-any-p-room', 'add-user-to-joined-room'], room._id); - } - - canSendMessage(roomId) { - // TODO: remove duplicated code - return ( - ChatSubscription.find({ - rid: roomId, - }).count() > 0 - ); - } - - allowRoomSettingChange(room, setting) { - switch (setting) { - case RoomSettingsEnum.JOIN_CODE: - return false; - case RoomSettingsEnum.BROADCAST: - return room.broadcast; - case RoomSettingsEnum.READ_ONLY: - return !room.broadcast; - case RoomSettingsEnum.REACT_WHEN_READ_ONLY: - return !room.broadcast && room.ro; - case RoomSettingsEnum.E2E: - return settings.get('E2E_Enable') === true; - case RoomSettingsEnum.SYSTEM_MESSAGES: - default: - return true; - } - } - - allowMemberAction(room, action) { - switch (action) { - case RoomMemberActions.BLOCK: - return false; - default: - return true; - } - } - - enableMembersListProfile() { - return true; - } - - getUiText(context) { - switch (context) { - case UiTextContext.HIDE_WARNING: - return 'Hide_Group_Warning'; - case UiTextContext.LEAVE_WARNING: - return 'Leave_Group_Warning'; - default: - return ''; - } - } - - getAvatarPath(roomData) { - return getAvatarURL({ roomId: roomData._id, cache: roomData.avatarETag }); - } - - includeInDashboard() { - return true; - } -} diff --git a/app/lib/lib/roomTypes/public.js b/app/lib/lib/roomTypes/public.js deleted file mode 100644 index 7282b4a00c56..000000000000 --- a/app/lib/lib/roomTypes/public.js +++ /dev/null @@ -1,150 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import { openRoom } from '../../../ui-utils'; -import { ChatRoom, ChatSubscription } from '../../../models'; -import { settings } from '../../../settings'; -import { hasAtLeastOnePermission } from '../../../authorization'; -import { getUserPreference, RoomTypeConfig, RoomTypeRouteConfig, RoomSettingsEnum, UiTextContext, RoomMemberActions } from '../../../utils'; -import { getAvatarURL } from '../../../utils/lib/getAvatarURL'; - -export class PublicRoomRoute extends RoomTypeRouteConfig { - constructor() { - super({ - name: 'channel', - path: '/channel/:name/:tab?/:context?', - }); - } - - action(params) { - return openRoom('c', params.name); - } -} - -export class PublicRoomType extends RoomTypeConfig { - constructor() { - super({ - identifier: 'c', - order: 30, - icon: 'hashtag', - label: 'Channels', - route: new PublicRoomRoute(), - }); - } - - getIcon(roomData) { - if (roomData.prid) { - return 'discussion'; - } - if (roomData.teamMain) { - return 'team'; - } - return this.icon; - } - - findRoom(identifier) { - const query = { - t: 'c', - name: identifier, - }; - return ChatRoom.findOne(query); - } - - roomName(roomData) { - if (roomData.prid) { - return roomData.fname; - } - if (settings.get('UI_Allow_room_names_with_special_chars')) { - return roomData.fname || roomData.name; - } - return roomData.name; - } - - condition() { - const groupByType = getUserPreference(Meteor.userId(), 'sidebarGroupByType'); - return ( - groupByType && (hasAtLeastOnePermission(['view-c-room', 'view-joined-room']) || settings.get('Accounts_AllowAnonymousRead') === true) - ); - } - - showJoinLink(roomId) { - return !!ChatRoom.findOne({ _id: roomId, t: 'c' }); - } - - includeInRoomSearch() { - return true; - } - - isGroupChat() { - return true; - } - - includeInDashboard() { - return true; - } - - canAddUser(room) { - return hasAtLeastOnePermission(['add-user-to-any-c-room', 'add-user-to-joined-room'], room._id); - } - - canSendMessage(roomId) { - const room = ChatRoom.findOne({ _id: roomId, t: 'c' }, { fields: { prid: 1 } }); - if (room.prid) { - return true; - } - - // TODO: remove duplicated code - return ( - ChatSubscription.find({ - rid: roomId, - }).count() > 0 - ); - } - - enableMembersListProfile() { - return true; - } - - allowRoomSettingChange(room, setting) { - switch (setting) { - case RoomSettingsEnum.BROADCAST: - return room.broadcast; - case RoomSettingsEnum.READ_ONLY: - return !room.broadcast; - case RoomSettingsEnum.REACT_WHEN_READ_ONLY: - return !room.broadcast && room.ro; - case RoomSettingsEnum.E2E: - return false; - case RoomSettingsEnum.SYSTEM_MESSAGES: - default: - return true; - } - } - - allowMemberAction(room, action) { - switch (action) { - case RoomMemberActions.BLOCK: - return false; - default: - return true; - } - } - - getUiText(context) { - switch (context) { - case UiTextContext.HIDE_WARNING: - return 'Hide_Room_Warning'; - case UiTextContext.LEAVE_WARNING: - return 'Leave_Room_Warning'; - default: - return ''; - } - } - - getAvatarPath(roomData) { - return getAvatarURL({ roomId: roomData._id, cache: roomData.avatarETag }); - } - - getDiscussionType() { - return 'c'; - } -} diff --git a/app/lib/lib/roomTypes/unread.js b/app/lib/lib/roomTypes/unread.js deleted file mode 100644 index 0422f8a46680..000000000000 --- a/app/lib/lib/roomTypes/unread.js +++ /dev/null @@ -1,19 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import { getUserPreference, RoomTypeConfig } from '../../../utils'; - -export class UnreadRoomType extends RoomTypeConfig { - constructor() { - super({ - identifier: 'unread', - order: 10, - label: 'Unread', - }); - - this.unread = true; - } - - condition() { - return getUserPreference(Meteor.userId(), 'sidebarShowUnread'); - } -} diff --git a/app/lib/server/functions/addUserToDefaultChannels.js b/app/lib/server/functions/addUserToDefaultChannels.ts similarity index 68% rename from app/lib/server/functions/addUserToDefaultChannels.js rename to app/lib/server/functions/addUserToDefaultChannels.ts index 3dc69de915c5..74c5061d3a33 100644 --- a/app/lib/server/functions/addUserToDefaultChannels.js +++ b/app/lib/server/functions/addUserToDefaultChannels.ts @@ -1,12 +1,14 @@ -import { Rooms, Subscriptions, Messages } from '../../../models'; +import { Rooms, Subscriptions, Messages } from '../../../models/server'; import { callbacks } from '../../../../lib/callbacks'; +import { IRoom } from '../../../../definition/IRoom'; +import { IUser } from '../../../../definition/IUser'; -export const addUserToDefaultChannels = function (user, silenced) { +export const addUserToDefaultChannels = function (user: IUser, silenced: boolean): void { callbacks.run('beforeJoinDefaultChannels', user); const defaultRooms = Rooms.findByDefaultAndTypes(true, ['c', 'p'], { fields: { usernames: 0 }, }).fetch(); - defaultRooms.forEach((room) => { + defaultRooms.forEach((room: IRoom) => { if (!Subscriptions.findOneByRoomIdAndUserId(room._id, user._id)) { // Add a subscription to this user Subscriptions.createWithRoomAndUser(room, user, { diff --git a/app/lib/server/functions/addUserToRoom.js b/app/lib/server/functions/addUserToRoom.ts similarity index 68% rename from app/lib/server/functions/addUserToRoom.js rename to app/lib/server/functions/addUserToRoom.ts index 40e356846341..c9da63f45805 100644 --- a/app/lib/server/functions/addUserToRoom.js +++ b/app/lib/server/functions/addUserToRoom.ts @@ -3,16 +3,27 @@ import { Meteor } from 'meteor/meteor'; import { AppEvents, Apps } from '../../../apps/server'; import { callbacks } from '../../../../lib/callbacks'; -import { Messages, Rooms, Subscriptions } from '../../../models'; +import { Messages, Rooms, Subscriptions } from '../../../models/server'; import { Team } from '../../../../server/sdk'; -import { RoomMemberActions, roomTypes } from '../../../utils/server'; - -export const addUserToRoom = function (rid, user, inviter, silenced) { +import { RoomMemberActions } from '../../../../definition/IRoomTypeConfig'; +import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; +import { IUser } from '../../../../definition/IUser'; +import { IRoom } from '../../../../definition/IRoom'; + +export const addUserToRoom = function ( + rid: string, + user: Pick, + inviter?: Pick, + silenced?: boolean, +): boolean | unknown { const now = new Date(); - const room = Rooms.findOneById(rid); + const room: IRoom = Rooms.findOneById(rid); - const roomConfig = roomTypes.getConfig(room.t); - if (!roomConfig.allowMemberAction(room, RoomMemberActions.JOIN) && !roomConfig.allowMemberAction(room, RoomMemberActions.INVITE)) { + const roomDirectives = roomCoordinator.getRoomDirectives(room.t); + if ( + !roomDirectives?.allowMemberAction(room, RoomMemberActions.JOIN) && + !roomDirectives?.allowMemberAction(room, RoomMemberActions.INVITE) + ) { return; } @@ -61,13 +72,18 @@ export const addUserToRoom = function (rid, user, inviter, silenced) { if (!silenced) { if (inviter) { - Messages.createUserAddedWithRoomIdAndUser(rid, user, { + const extraData = { ts: now, u: { _id: inviter._id, username: inviter.username, }, - }); + }; + if (room.teamMain) { + Messages.createUserAddedToTeamWithRoomIdAndUser(rid, user, extraData); + } else { + Messages.createUserAddedWithRoomIdAndUser(rid, user, extraData); + } } else if (room.prid) { Messages.createUserJoinWithRoomIdAndUserDiscussion(rid, user, { ts: now }); } else if (room.teamMain) { @@ -89,7 +105,7 @@ export const addUserToRoom = function (rid, user, inviter, silenced) { }); } - if (room.teamMain && room.teamId) { + if (room.teamMain && room.teamId && inviter) { // if user is joining to main team channel, create a membership Promise.await(Team.addMember(inviter, user._id, room.teamId)); } diff --git a/app/lib/server/functions/archiveRoom.js b/app/lib/server/functions/archiveRoom.ts similarity index 54% rename from app/lib/server/functions/archiveRoom.js rename to app/lib/server/functions/archiveRoom.ts index 5fb20a5dc3e3..335a73b85aac 100644 --- a/app/lib/server/functions/archiveRoom.js +++ b/app/lib/server/functions/archiveRoom.ts @@ -1,11 +1,12 @@ import { Meteor } from 'meteor/meteor'; -import { Rooms, Subscriptions } from '../../../models'; +import { Rooms, Messages, Subscriptions } from '../../../models/server'; import { callbacks } from '../../../../lib/callbacks'; -export const archiveRoom = function (rid) { +export const archiveRoom = function (rid: string): void { Rooms.archiveById(rid); Subscriptions.archiveByRoomId(rid); + Messages.createRoomArchivedByRoomIdAndUser(rid, Meteor.user()); callbacks.run('afterRoomArchived', Rooms.findOneById(rid), Meteor.user()); }; diff --git a/app/lib/server/functions/attachMessage.js b/app/lib/server/functions/attachMessage.js deleted file mode 100644 index 7de251417e37..000000000000 --- a/app/lib/server/functions/attachMessage.js +++ /dev/null @@ -1,20 +0,0 @@ -import { getUserAvatarURL } from '../../../utils/lib/getUserAvatarURL'; -import { roomTypes } from '../../../utils'; - -export const attachMessage = function (message, room) { - const { - msg, - u: { username }, - ts, - attachments, - _id, - } = message; - return { - text: msg, - author_name: username, - author_icon: getUserAvatarURL(username), - message_link: `${roomTypes.getRouteLink(room.t, room)}?msg=${_id}`, - attachments, - ts, - }; -}; diff --git a/app/lib/server/functions/attachMessage.ts b/app/lib/server/functions/attachMessage.ts new file mode 100644 index 000000000000..da8c500f266b --- /dev/null +++ b/app/lib/server/functions/attachMessage.ts @@ -0,0 +1,34 @@ +import { getUserAvatarURL } from '../../../utils/lib/getUserAvatarURL'; +import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; +import { IMessage } from '../../../../definition/IMessage'; +import { IRoom } from '../../../../definition/IRoom'; +import { MessageAttachment } from '../../../../definition/IMessage/MessageAttachment/MessageAttachment'; + +export const attachMessage = function ( + message: IMessage, + room: IRoom, +): { + text: string; + authorName?: string; + authorIcon: string; + message_link: string; + attachments?: MessageAttachment[]; + ts: Date; +} { + const { + msg, + u: { username }, + ts, + attachments, + _id, + } = message; + return { + text: msg, + authorName: username, + authorIcon: getUserAvatarURL(username), + // eslint-disable-next-line @typescript-eslint/camelcase + message_link: `${roomCoordinator.getRouteLink(room.t, room)}?msg=${_id}`, + attachments, + ts, + }; +}; diff --git a/app/lib/server/functions/checkEmailAvailability.js b/app/lib/server/functions/checkEmailAvailability.ts similarity index 77% rename from app/lib/server/functions/checkEmailAvailability.js rename to app/lib/server/functions/checkEmailAvailability.ts index e27307dee81f..b946706dc6b8 100644 --- a/app/lib/server/functions/checkEmailAvailability.js +++ b/app/lib/server/functions/checkEmailAvailability.ts @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import s from 'underscore.string'; import { escapeRegExp } from '@rocket.chat/string-helpers'; -export const checkEmailAvailability = function (email) { +export const checkEmailAvailability = function (email: string): boolean { return !Meteor.users.findOne({ 'emails.address': { $regex: new RegExp(`^${s.trim(escapeRegExp(email))}$`, 'i') }, }); diff --git a/app/lib/server/functions/checkUsernameAvailability.js b/app/lib/server/functions/checkUsernameAvailability.ts similarity index 75% rename from app/lib/server/functions/checkUsernameAvailability.js rename to app/lib/server/functions/checkUsernameAvailability.ts index 60a7c31abc90..f98414ea23ed 100644 --- a/app/lib/server/functions/checkUsernameAvailability.js +++ b/app/lib/server/functions/checkUsernameAvailability.ts @@ -7,18 +7,18 @@ import { settings } from '../../../settings/server'; import { Team } from '../../../../server/sdk'; import { validateName } from './validateName'; -let usernameBlackList = []; +let usernameBlackList: RegExp[] = []; -const toRegExp = (username) => new RegExp(`^${escapeRegExp(username).trim()}$`, 'i'); +const toRegExp = (username: string): RegExp => new RegExp(`^${escapeRegExp(username).trim()}$`, 'i'); -settings.watch('Accounts_BlockedUsernameList', (value) => { +settings.watch('Accounts_BlockedUsernameList', (value: string) => { usernameBlackList = ['all', 'here'].concat(value.split(',')).map(toRegExp); }); -const usernameIsBlocked = (username, usernameBlackList) => +const usernameIsBlocked = (username: string, usernameBlackList: RegExp[]): boolean | number => usernameBlackList.length && usernameBlackList.some((restrictedUsername) => restrictedUsername.test(s.trim(escapeRegExp(username)))); -export const checkUsernameAvailability = function (username) { +export const checkUsernameAvailability = function (username: string): boolean { if (usernameIsBlocked(username, usernameBlackList) || !validateName(username)) { throw new Meteor.Error('error-blocked-username', `${_.escape(username)} is blocked and can't be used!`, { method: 'checkUsernameAvailability', diff --git a/app/lib/server/functions/cleanRoomHistory.js b/app/lib/server/functions/cleanRoomHistory.ts similarity index 71% rename from app/lib/server/functions/cleanRoomHistory.js rename to app/lib/server/functions/cleanRoomHistory.ts index 04f344a33dd7..8021a424d839 100644 --- a/app/lib/server/functions/cleanRoomHistory.js +++ b/app/lib/server/functions/cleanRoomHistory.ts @@ -1,12 +1,14 @@ +import { Cursor } from 'mongodb'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { deleteRoom } from './deleteRoom'; import { FileUpload } from '../../../file-upload/server'; import { Messages, Rooms, Subscriptions } from '../../../models/server'; -import { Notifications } from '../../../notifications/server'; +import { api } from '../../../../server/sdk/api'; +import { IMessage, IMessageDiscussion } from '../../../../definition/IMessage'; export const cleanRoomHistory = function ({ - rid, + rid = '', latest = new Date(), oldest = new Date('0001-01-01T00:00:00Z'), inclusive = true, @@ -16,7 +18,7 @@ export const cleanRoomHistory = function ({ filesOnly = false, fromUsers = [], ignoreThreads = true, -}) { +}): unknown { const gt = inclusive ? '$gte' : '$gt'; const lt = inclusive ? '$lte' : '$lt'; @@ -28,8 +30,8 @@ export const cleanRoomHistory = function ({ Messages.findFilesByRoomIdPinnedTimestampAndUsers(rid, excludePinned, ignoreDiscussion, ts, fromUsers, ignoreThreads, { fields: { 'file._id': 1, 'pinned': 1 }, limit, - }).forEach((document) => { - FileUpload.getStore('Uploads').deleteById(document.file._id); + }).forEach((document: IMessage) => { + FileUpload.getStore('Uploads').deleteById(document.file?._id); fileCount++; if (filesOnly) { Messages.update({ _id: document._id }, { $unset: { file: 1 }, $set: { attachments: [{ color: '#FD745E', text }] } }); @@ -41,16 +43,14 @@ export const cleanRoomHistory = function ({ } if (!ignoreDiscussion) { - Messages.findDiscussionByRoomIdPinnedTimestampAndUsers( - rid, - excludePinned, - ts, - fromUsers, - { fields: { drid: 1 }, ...(limit && { limit }) }, - ignoreThreads, - ) - .fetch() - .forEach(({ drid }) => deleteRoom(drid)); + Promise.await( + ( + Messages.findDiscussionByRoomIdPinnedTimestampAndUsers(rid, excludePinned, ts, fromUsers, { + fields: { drid: 1 }, + ...(limit && { limit }), + }) as Cursor + ).forEach(({ drid }) => deleteRoom(drid)), + ); } if (!ignoreThreads) { @@ -58,7 +58,7 @@ export const cleanRoomHistory = function ({ Messages.findThreadsByRoomIdPinnedTimestampAndUsers( { rid, pinned: excludePinned, ignoreDiscussion, ts, users: fromUsers }, { fields: { _id: 1 } }, - ).forEach(({ _id }) => threads.add(_id)); + ).forEach(({ _id }: { _id: string }) => threads.add(_id)); if (threads.size > 0) { Subscriptions.removeUnreadThreadsByRoomId(rid, [...threads]); @@ -68,7 +68,7 @@ export const cleanRoomHistory = function ({ const count = Messages.removeByIdPinnedTimestampLimitAndUsers(rid, excludePinned, ignoreDiscussion, ts, limit, fromUsers, ignoreThreads); if (count) { Rooms.resetLastMessageById(rid); - Notifications.notifyRoom(rid, 'deleteMessageBulk', { + api.broadcast('notify.deleteMessageBulk', rid, { rid, excludePinned, ignoreDiscussion, diff --git a/app/lib/server/functions/createDirectRoom.js b/app/lib/server/functions/createDirectRoom.ts similarity index 66% rename from app/lib/server/functions/createDirectRoom.js rename to app/lib/server/functions/createDirectRoom.ts index 9a48c1581481..514618e6f2a8 100644 --- a/app/lib/server/functions/createDirectRoom.js +++ b/app/lib/server/functions/createDirectRoom.ts @@ -8,8 +8,10 @@ import { Rooms } from '../../../models/server'; import { settings } from '../../../settings/server'; import { getDefaultSubscriptionPref } from '../../../utils/server'; import { Users, Subscriptions } from '../../../models/server/raw'; +import { IUser } from '../../../../definition/IUser'; +import { ICreateRoomParams } from '../../../../server/sdk/types/IRoomService'; -const generateSubscription = (fname, name, user, extra) => ({ +const generateSubscription = (fname: string, name: string, user: IUser, extra: {}): any => ({ _id: Random.id(), alert: false, unread: 0, @@ -27,15 +29,16 @@ const generateSubscription = (fname, name, user, extra) => ({ }, }); -const getFname = (members) => members.map(({ name, username }) => name || username).join(', '); -const getName = (members) => members.map(({ username }) => username).join(', '); +const getFname = (members: IUser[]): string => members.map(({ name, username }) => name || username).join(', '); +const getName = (members: IUser[]): string => members.map(({ username }) => username).join(', '); -export const createDirectRoom = function (members, roomExtraData = {}, options = {}) { +export const createDirectRoom = function (members: IUser[], roomExtraData = {}, options: ICreateRoomParams['options']): unknown { if (members.length > (settings.get('DirectMesssage_maxUsers') || 1)) { throw new Error('error-direct-message-max-user-exceeded'); } - const sortedMembers = members.sort((u1, u2) => (u1.name || u1.username).localeCompare(u2.name || u2.username)); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const sortedMembers = members.sort((u1, u2) => (u1.name! || u1.username!).localeCompare(u2.name! || u2.username!)); const usernames = sortedMembers.map(({ username }) => username); const uids = members.map(({ _id }) => _id).sort(); @@ -60,10 +63,13 @@ export const createDirectRoom = function (members, roomExtraData = {}, options = }; if (isNewRoom) { - roomInfo._USERNAMES = usernames; + const tmpRoom = { + ...roomInfo, + _USERNAMES: usernames, + }; const prevent = Promise.await( - Apps.triggerEvent('IPreRoomCreatePrevent', roomInfo).catch((error) => { + Apps.triggerEvent('IPreRoomCreatePrevent', tmpRoom).catch((error) => { if (error instanceof AppsEngineException) { throw new Meteor.Error('error-app-prevented', error.message); } @@ -75,15 +81,13 @@ export const createDirectRoom = function (members, roomExtraData = {}, options = throw new Meteor.Error('error-app-prevented', 'A Rocket.Chat App prevented the room creation.'); } - let result; - result = Promise.await(Apps.triggerEvent('IPreRoomCreateExtend', roomInfo)); - result = Promise.await(Apps.triggerEvent('IPreRoomCreateModify', result)); + const result = Promise.await( + Apps.triggerEvent('IPreRoomCreateModify', Promise.await(Apps.triggerEvent('IPreRoomCreateExtend', tmpRoom))), + ); if (typeof result === 'object') { Object.assign(roomInfo, result); } - - delete roomInfo._USERNAMES; } const rid = room?._id || Rooms.insert(roomInfo); @@ -94,25 +98,26 @@ export const createDirectRoom = function (members, roomExtraData = {}, options = { rid, 'u._id': members[0]._id }, { $set: { open: true }, - $setOnInsert: generateSubscription(members[0].name || members[0].username, members[0].username, members[0], { - ...options.subscriptionExtra, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + $setOnInsert: generateSubscription(members[0].name! || members[0].username!, members[0].username!, members[0], { + ...options?.subscriptionExtra, }), }, { upsert: true }, ); } else { const memberIds = members.map((member) => member._id); - const membersWithPreferences = Users.find({ _id: { $in: memberIds } }, { 'username': 1, 'settings.preferences': 1 }); + const membersWithPreferences = Users.find({ _id: { $in: memberIds } }, { projection: { 'username': 1, 'settings.preferences': 1 } }); membersWithPreferences.forEach((member) => { const otherMembers = sortedMembers.filter(({ _id }) => _id !== member._id); Subscriptions.updateOne( { rid, 'u._id': member._id }, { - ...(options.creator === member._id && { $set: { open: true } }), + ...(options?.creator === member._id && { $set: { open: true } }), $setOnInsert: generateSubscription(getFname(otherMembers), getName(otherMembers), member, { - ...options.subscriptionExtra, - ...(options.creator !== member._id && { open: members.length > 2 }), + ...options?.subscriptionExtra, + ...(options?.creator !== member._id && { open: members.length > 2 }), }), }, { upsert: true }, diff --git a/app/lib/server/functions/createRoom.js b/app/lib/server/functions/createRoom.js deleted file mode 100644 index 2569f8ea5df0..000000000000 --- a/app/lib/server/functions/createRoom.js +++ /dev/null @@ -1,153 +0,0 @@ -import { AppsEngineException } from '@rocket.chat/apps-engine/definition/exceptions'; -import { Meteor } from 'meteor/meteor'; -import _ from 'underscore'; -import s from 'underscore.string'; - -import { Apps } from '../../../apps/server'; -import { addUserRoles } from '../../../authorization'; -import { callbacks } from '../../../../lib/callbacks'; -import { Rooms, Subscriptions, Users } from '../../../models'; -import { getValidRoomName } from '../../../utils'; -import { createDirectRoom } from './createDirectRoom'; -import { Team } from '../../../../server/sdk'; - -export const createRoom = function (type, name, owner, members = [], readOnly, { teamId, ...extraData } = {}, options = {}) { - callbacks.run('beforeCreateRoom', { type, name, owner, members, readOnly, extraData, options }); - - if (type === 'd') { - return createDirectRoom(members, extraData, options); - } - - name = s.trim(name); - owner = s.trim(owner); - members = [].concat(members); - - if (!name) { - throw new Meteor.Error('error-invalid-name', 'Invalid name', { - function: 'RocketChat.createRoom', - }); - } - - owner = Users.findOneByUsernameIgnoringCase(owner, { fields: { username: 1 } }); - - if (!owner) { - throw new Meteor.Error('error-invalid-user', 'Invalid user', { - function: 'RocketChat.createRoom', - }); - } - - if (!_.contains(members, owner.username)) { - members.push(owner.username); - } - - if (extraData.broadcast) { - readOnly = true; - delete extraData.reactWhenReadOnly; - } - - const now = new Date(); - - const validRoomNameOptions = {}; - - if (options.nameValidationRegex) { - validRoomNameOptions.nameValidationRegex = options.nameValidationRegex; - } - - let room = { - fname: name, - ...extraData, - name: getValidRoomName(name, null, validRoomNameOptions), - t: type, - msgs: 0, - usersCount: 0, - u: { - _id: owner._id, - username: owner.username, - }, - ts: now, - ro: readOnly === true, - }; - - if (teamId) { - const team = Promise.await(Team.getOneById(teamId, { projection: { _id: 1 } })); - if (team) { - room.teamId = team._id; - } - } - - room._USERNAMES = members; - - const prevent = Promise.await( - Apps.triggerEvent('IPreRoomCreatePrevent', room).catch((error) => { - if (error instanceof AppsEngineException) { - throw new Meteor.Error('error-app-prevented', error.message); - } - - throw error; - }), - ); - - if (prevent) { - throw new Meteor.Error('error-app-prevented', 'A Rocket.Chat App prevented the room creation.'); - } - - let result; - result = Promise.await(Apps.triggerEvent('IPreRoomCreateExtend', room)); - result = Promise.await(Apps.triggerEvent('IPreRoomCreateModify', result)); - - if (typeof result === 'object') { - Object.assign(room, result); - } - - delete room._USERNAMES; - - if (type === 'c') { - callbacks.run('beforeCreateChannel', owner, room); - } - room = Rooms.createWithFullRoomData(room); - - for (const username of members) { - const member = Users.findOneByUsername(username, { - fields: { 'username': 1, 'settings.preferences': 1 }, - }); - if (!member) { - continue; - } - - const extra = options.subscriptionExtra || {}; - - extra.open = true; - - if (room.prid) { - extra.prid = room.prid; - } - - if (username === owner.username) { - extra.ls = now; - } - - Subscriptions.createWithRoomAndUser(room, member, extra); - } - - addUserRoles(owner._id, ['owner'], room._id); - - if (type === 'c') { - Meteor.defer(() => { - callbacks.run('afterCreateChannel', owner, room); - }); - } else if (type === 'p') { - Meteor.defer(() => { - callbacks.run('afterCreatePrivateGroup', owner, room); - }); - } - Meteor.defer(() => { - callbacks.run('afterCreateRoom', owner, room); - }); - - Apps.triggerEvent('IPostRoomCreate', room); - - return { - rid: room._id, // backwards compatible - ...room, - }; -}; diff --git a/app/lib/server/functions/createRoom.ts b/app/lib/server/functions/createRoom.ts new file mode 100644 index 000000000000..95b157fcd000 --- /dev/null +++ b/app/lib/server/functions/createRoom.ts @@ -0,0 +1,160 @@ +import { AppsEngineException } from '@rocket.chat/apps-engine/definition/exceptions'; +import { Meteor } from 'meteor/meteor'; +import _ from 'underscore'; +import s from 'underscore.string'; + +import { Apps } from '../../../apps/server'; +import { addUserRoles } from '../../../../server/lib/roles/addUserRoles'; +import { callbacks } from '../../../../lib/callbacks'; +import { Messages, Rooms, Subscriptions, Users } from '../../../models/server'; +import { getValidRoomName } from '../../../utils/server'; +import { createDirectRoom } from './createDirectRoom'; +import { Team } from '../../../../server/sdk'; +import { IUser } from '../../../../definition/IUser'; +import { ICreateRoomParams, ISubscriptionExtraData } from '../../../../server/sdk/types/IRoomService'; +import { IRoom, RoomType } from '../../../../definition/IRoom'; + +const isValidName = (name: unknown): name is string => { + return typeof name === 'string' && s.trim(name).length > 0; +}; + +export const createRoom = function ( + type: T, + name: T extends 'd' ? undefined : string, + ownerUsername: string, + members: T extends 'd' ? IUser[] : string[] = [], + readOnly?: boolean, + roomExtraData?: Partial, + options?: ICreateRoomParams['options'], +): unknown { + const { teamId, ...extraData } = roomExtraData || ({} as IRoom); + callbacks.run('beforeCreateRoom', { type, name, owner: ownerUsername, members, readOnly, extraData, options }); + + if (type === 'd') { + return createDirectRoom(members as IUser[], extraData, options); + } + + if (!isValidName(name)) { + throw new Meteor.Error('error-invalid-name', 'Invalid name', { + function: 'RocketChat.createRoom', + }); + } + + const owner = Users.findOneByUsernameIgnoringCase(ownerUsername, { fields: { username: 1 } }); + + if (!owner) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { + function: 'RocketChat.createRoom', + }); + } + + if (!_.contains(members, owner)) { + members.push(owner.username); + } + + if (extraData.broadcast) { + readOnly = true; + delete extraData.reactWhenReadOnly; + } + + const now = new Date(); + + const roomProps: Omit = { + fname: name, + ...extraData, + name: getValidRoomName(name.trim(), undefined, { + ...(options?.nameValidationRegex && { nameValidationRegex: options.nameValidationRegex }), + }), + t: type, + msgs: 0, + usersCount: 0, + u: { + _id: owner._id, + username: owner.username, + }, + ts: now, + ro: readOnly === true, + }; + + if (teamId) { + const team = Promise.await(Team.getOneById(teamId, { projection: { _id: 1 } })); + if (team) { + roomProps.teamId = team._id; + } + } + + const tmp = { + ...roomProps, + _USERNAMES: members, + }; + + const prevent = Promise.await( + Apps.triggerEvent('IPreRoomCreatePrevent', tmp).catch((error) => { + if (error instanceof AppsEngineException) { + throw new Meteor.Error('error-app-prevented', error.message); + } + + throw error; + }), + ); + + if (prevent) { + throw new Meteor.Error('error-app-prevented', 'A Rocket.Chat App prevented the room creation.'); + } + + const { _USERNAMES, ...result } = Promise.await( + Apps.triggerEvent('IPreRoomCreateModify', Promise.await(Apps.triggerEvent('IPreRoomCreateExtend', tmp))), + ); + + if (typeof result === 'object') { + Object.assign(roomProps, result); + } + + if (type === 'c') { + callbacks.run('beforeCreateChannel', owner, roomProps); + } + const room = Rooms.createWithFullRoomData(roomProps); + + for (const username of [...new Set(members as string[])]) { + const member = Users.findOneByUsername(username, { + fields: { 'username': 1, 'settings.preferences': 1 }, + }); + if (!member) { + continue; + } + + const extra: Partial = options?.subscriptionExtra || {}; + + extra.open = true; + + if (room.prid) { + extra.prid = room.prid; + } + + if (username === owner.username) { + extra.ls = now; + } + + Subscriptions.createWithRoomAndUser(room, member, extra); + } + + addUserRoles(owner._id, ['owner'], room._id); + + if (type === 'c') { + if (room.teamId) { + const team = Promise.await(Team.getOneById(room.teamId)); + team && Messages.createUserAddRoomToTeamWithRoomIdAndUser(team.roomId, room.name, owner); + } + callbacks.run('afterCreateChannel', owner, room); + } else if (type === 'p') { + callbacks.runAsync('afterCreatePrivateGroup', owner, room); + } + callbacks.runAsync('afterCreateRoom', owner, room); + + Apps.triggerEvent('IPostRoomCreate', room); + + return { + rid: room._id, // backwards compatible + ...room, + }; +}; diff --git a/app/lib/server/functions/deleteMessage.ts b/app/lib/server/functions/deleteMessage.ts index b36b696ca4eb..77b29875fe89 100644 --- a/app/lib/server/functions/deleteMessage.ts +++ b/app/lib/server/functions/deleteMessage.ts @@ -4,7 +4,7 @@ import { FileUpload } from '../../../file-upload/server'; import { settings } from '../../../settings/server'; import { Messages, Rooms } from '../../../models/server'; import { Uploads } from '../../../models/server/raw'; -import { Notifications } from '../../../notifications/server'; +import { api } from '../../../../server/sdk/api'; import { callbacks } from '../../../../lib/callbacks'; import { Apps } from '../../../apps/server'; import { IMessage } from '../../../../definition/IMessage'; @@ -66,7 +66,7 @@ export const deleteMessage = async function (message: IMessage, user: IUser): Pr if (showDeletedStatus) { Messages.setAsDeletedByIdAndUser(message._id, user); } else { - Notifications.notifyRoom(message.rid, 'deleteMessage', { _id: message._id }); + api.broadcast('notify.deleteMessage', message.rid, { _id: message._id }); } if (bridges) { diff --git a/app/lib/server/functions/deleteRoom.js b/app/lib/server/functions/deleteRoom.ts similarity index 64% rename from app/lib/server/functions/deleteRoom.js rename to app/lib/server/functions/deleteRoom.ts index 7d8b83bf7c6e..e74a54ebb9c7 100644 --- a/app/lib/server/functions/deleteRoom.js +++ b/app/lib/server/functions/deleteRoom.ts @@ -1,8 +1,10 @@ -import { Messages, Subscriptions, Rooms } from '../../../models'; +import { DeleteWriteOpResultObject } from 'mongodb'; + +import { Messages, Subscriptions, Rooms } from '../../../models/server'; import { callbacks } from '../../../../lib/callbacks'; import { FileUpload } from '../../../file-upload/server'; -export const deleteRoom = function (rid) { +export const deleteRoom = function (rid: string): Promise { FileUpload.removeFilesByRoomId(rid); Messages.removeByRoomId(rid); callbacks.run('beforeDeleteRoom', rid); diff --git a/app/lib/server/functions/deleteUser.js b/app/lib/server/functions/deleteUser.ts similarity index 93% rename from app/lib/server/functions/deleteUser.js rename to app/lib/server/functions/deleteUser.ts index dc53f0d70277..9925c2e78c94 100644 --- a/app/lib/server/functions/deleteUser.js +++ b/app/lib/server/functions/deleteUser.ts @@ -10,8 +10,9 @@ import { relinquishRoomOwnerships } from './relinquishRoomOwnerships'; import { getSubscribedRoomsForUserWithDetails, shouldRemoveOrChangeOwner } from './getRoomsWithSingleOwner'; import { getUserSingleOwnedRooms } from './getUserSingleOwnedRooms'; import { api } from '../../../../server/sdk/api'; +import { FileProp } from '../../../../definition/IMessage/MessageAttachment/Files/FileProp'; -export async function deleteUser(userId, confirmRelinquish = false) { +export async function deleteUser(userId: string, confirmRelinquish = false): Promise { const user = Users.findOneById(userId, { fields: { username: 1, avatarOrigin: 1, federation: 1 }, }); @@ -43,7 +44,7 @@ export async function deleteUser(userId, confirmRelinquish = false) { switch (messageErasureType) { case 'Delete': const store = FileUpload.getStore('Uploads'); - Messages.findFilesByUserId(userId).forEach(function ({ file }) { + Messages.findFilesByUserId(userId).forEach(function ({ file }: { file: FileProp }) { store.deleteById(file._id); }); Messages.removeByUserId(userId); diff --git a/app/lib/server/functions/getAvatarSuggestionForUser.js b/app/lib/server/functions/getAvatarSuggestionForUser.js index a59593a638cb..57d788d70649 100644 --- a/app/lib/server/functions/getAvatarSuggestionForUser.js +++ b/app/lib/server/functions/getAvatarSuggestionForUser.js @@ -1,9 +1,9 @@ import { check } from 'meteor/check'; -import { fetch } from 'meteor/fetch'; import { Gravatar } from 'meteor/jparker:gravatar'; import { ServiceConfiguration } from 'meteor/service-configuration'; -import { settings } from '../../../settings'; +import { settings } from '../../../settings/server'; +import { fetch } from '../../../../server/lib/http/fetch'; const avatarProviders = { facebook(user) { diff --git a/app/lib/server/functions/getDirectMessageByNameOrIdWithOptionToJoin.js b/app/lib/server/functions/getDirectMessageByNameOrIdWithOptionToJoin.js deleted file mode 100644 index a79098eb2fcd..000000000000 --- a/app/lib/server/functions/getDirectMessageByNameOrIdWithOptionToJoin.js +++ /dev/null @@ -1,6 +0,0 @@ -import { getRoomByNameOrIdWithOptionToJoin } from './getRoomByNameOrIdWithOptionToJoin'; - -export const getDirectMessageByNameOrIdWithOptionToJoin = (args) => getRoomByNameOrIdWithOptionToJoin({ ...args, type: 'd' }); - -export const getDirectMessageByIdWithOptionToJoin = (args) => - getDirectMessageByNameOrIdWithOptionToJoin({ ...args, tryDirectByUserIdOnly: true }); diff --git a/app/lib/server/functions/getDirectMessageByNameOrIdWithOptionToJoin.ts b/app/lib/server/functions/getDirectMessageByNameOrIdWithOptionToJoin.ts new file mode 100644 index 000000000000..b4fb4998f812 --- /dev/null +++ b/app/lib/server/functions/getDirectMessageByNameOrIdWithOptionToJoin.ts @@ -0,0 +1,6 @@ +import { getRoomByNameOrIdWithOptionToJoin } from './getRoomByNameOrIdWithOptionToJoin'; + +export const getDirectMessageByNameOrIdWithOptionToJoin = (args: {}): unknown => getRoomByNameOrIdWithOptionToJoin({ ...args, type: 'd' }); + +export const getDirectMessageByIdWithOptionToJoin = (args: {}): unknown => + getDirectMessageByNameOrIdWithOptionToJoin({ ...args, tryDirectByUserIdOnly: true }); diff --git a/app/lib/server/functions/getFullUserData.js b/app/lib/server/functions/getFullUserData.js index dddaafce3872..d328f3b05832 100644 --- a/app/lib/server/functions/getFullUserData.js +++ b/app/lib/server/functions/getFullUserData.js @@ -17,6 +17,7 @@ const defaultFields = { reason: 1, statusText: 1, avatarETag: 1, + extension: 1, }; const fullFields = { diff --git a/app/lib/server/functions/getRoomByNameOrIdWithOptionToJoin.js b/app/lib/server/functions/getRoomByNameOrIdWithOptionToJoin.ts similarity index 90% rename from app/lib/server/functions/getRoomByNameOrIdWithOptionToJoin.js rename to app/lib/server/functions/getRoomByNameOrIdWithOptionToJoin.ts index e6a5f5d3d9ad..5d03cc17ff99 100644 --- a/app/lib/server/functions/getRoomByNameOrIdWithOptionToJoin.js +++ b/app/lib/server/functions/getRoomByNameOrIdWithOptionToJoin.ts @@ -1,17 +1,19 @@ import { Meteor } from 'meteor/meteor'; import _ from 'underscore'; -import { Rooms, Users, Subscriptions } from '../../../models'; +import { IRoom } from '../../../../definition/IRoom'; +import { IUser } from '../../../../definition/IUser'; +import { Rooms, Users, Subscriptions } from '../../../models/server'; export const getRoomByNameOrIdWithOptionToJoin = function _getRoomByNameOrIdWithOptionToJoin({ - currentUserId, - nameOrId, + currentUserId = '', + nameOrId = '', type = '', tryDirectByUserIdOnly = false, joinChannel = true, errorOnEmpty = true, -}) { - let room; +}): any { + let room: IRoom; // If the nameOrId starts with #, then let's try to find a channel or group if (nameOrId.startsWith('#')) { @@ -21,7 +23,7 @@ export const getRoomByNameOrIdWithOptionToJoin = function _getRoomByNameOrIdWith // If the nameOrId starts with @ OR type is 'd', then let's try just a direct message nameOrId = nameOrId.replace('@', ''); - let roomUser; + let roomUser: IUser; if (tryDirectByUserIdOnly) { roomUser = Users.findOneById(nameOrId); } else { diff --git a/app/lib/server/functions/getRoomsWithSingleOwner.js b/app/lib/server/functions/getRoomsWithSingleOwner.ts similarity index 67% rename from app/lib/server/functions/getRoomsWithSingleOwner.js rename to app/lib/server/functions/getRoomsWithSingleOwner.ts index 2b268b60b863..db361ffd1ca1 100644 --- a/app/lib/server/functions/getRoomsWithSingleOwner.js +++ b/app/lib/server/functions/getRoomsWithSingleOwner.ts @@ -1,19 +1,36 @@ +import { Cursor } from 'mongodb'; + import { subscriptionHasRole } from '../../../authorization/server'; import { Users, Subscriptions } from '../../../models/server'; +import { IUser } from '../../../../definition/IUser'; +import { ISubscription } from '../../../../definition/ISubscription'; + +export type SubscribedRoomsForUserWithDetails = { + rid: string; + t: string; + shouldBeRemoved: boolean; + shouldChangeOwner: boolean; + userIsLastOwner: boolean; + newOwner: IUser['_id'] | null; +}; -export function shouldRemoveOrChangeOwner(subscribedRooms) { +export function shouldRemoveOrChangeOwner(subscribedRooms: SubscribedRoomsForUserWithDetails[]): boolean { return subscribedRooms.some(({ shouldBeRemoved, shouldChangeOwner }) => shouldBeRemoved || shouldChangeOwner); } -export function getSubscribedRoomsForUserWithDetails(userId, assignNewOwner = true, roomIds = []) { - const subscribedRooms = []; +export function getSubscribedRoomsForUserWithDetails( + userId: string, + assignNewOwner = true, + roomIds: string[] = [], +): SubscribedRoomsForUserWithDetails[] { + const subscribedRooms: SubscribedRoomsForUserWithDetails[] = []; - const cursor = + const cursor: Cursor = roomIds.length > 0 ? Subscriptions.findByUserIdAndRoomIds(userId, roomIds) : Subscriptions.findByUserIdExceptType(userId, 'd'); // Iterate through all the rooms the user is subscribed to, to check if he is the last owner of any of them. cursor.forEach((subscription) => { - const roomData = { + const roomData: SubscribedRoomsForUserWithDetails = { rid: subscription.rid, t: subscription.t, shouldBeRemoved: false, @@ -29,8 +46,8 @@ export function getSubscribedRoomsForUserWithDetails(userId, assignNewOwner = tr roomData.userIsLastOwner = numOwners === 1; if (numOwners === 1 && assignNewOwner) { // Let's check how many subscribers the room has. - const options = { fields: { 'u._id': 1 }, sort: { ts: 1 } }; - const subscribersCursor = Subscriptions.findByRoomId(subscription.rid, options); + const options = { projection: { 'u._id': 1 }, sort: { ts: 1 } }; + const subscribersCursor: Cursor = Subscriptions.findByRoomId(subscription.rid, options); subscribersCursor.forEach(({ u: { _id: uid } }) => { // If we already changed the owner or this subscription is for the user we are removing, then don't try to give it ownership diff --git a/app/lib/server/functions/getStatusText.js b/app/lib/server/functions/getStatusText.ts similarity index 71% rename from app/lib/server/functions/getStatusText.js rename to app/lib/server/functions/getStatusText.ts index a1d3589112d0..af154b24d3fe 100644 --- a/app/lib/server/functions/getStatusText.js +++ b/app/lib/server/functions/getStatusText.ts @@ -1,6 +1,6 @@ import { Users } from '../../../models/server'; -export const getStatusText = function (userId) { +export const getStatusText = function (userId: string): unknown { if (!userId) { return undefined; } @@ -15,5 +15,5 @@ export const getStatusText = function (userId) { }; const data = Users.findOneById(userId, options); - return data && data.statusText; + return data?.statusText; }; diff --git a/app/lib/server/functions/getUserSingleOwnedRooms.js b/app/lib/server/functions/getUserSingleOwnedRooms.js deleted file mode 100644 index 04f339e2dc76..000000000000 --- a/app/lib/server/functions/getUserSingleOwnedRooms.js +++ /dev/null @@ -1,25 +0,0 @@ -import { Rooms } from '../../../models/server'; - -export const getUserSingleOwnedRooms = function (subscribedRooms) { - const roomsThatWillChangeOwner = subscribedRooms.filter(({ shouldChangeOwner }) => shouldChangeOwner).map(({ rid }) => rid); - const roomsThatWillBeRemoved = subscribedRooms.filter(({ shouldBeRemoved }) => shouldBeRemoved).map(({ rid }) => rid); - - const roomIds = roomsThatWillBeRemoved.concat(roomsThatWillChangeOwner); - const rooms = Rooms.findByIds(roomIds, { fields: { _id: 1, name: 1, fname: 1 } }); - - const result = { - shouldBeRemoved: [], - shouldChangeOwner: [], - }; - - rooms.forEach((room) => { - const name = room.fname || room.name; - if (roomsThatWillBeRemoved.includes(room._id)) { - result.shouldBeRemoved.push(name); - } else { - result.shouldChangeOwner.push(name); - } - }); - - return result; -}; diff --git a/app/lib/server/functions/getUserSingleOwnedRooms.ts b/app/lib/server/functions/getUserSingleOwnedRooms.ts new file mode 100644 index 000000000000..9bf663cf1d34 --- /dev/null +++ b/app/lib/server/functions/getUserSingleOwnedRooms.ts @@ -0,0 +1,31 @@ +import { IRoom } from '../../../../definition/IRoom'; +import { Rooms } from '../../../models/server'; +import { SubscribedRoomsForUserWithDetails } from './getRoomsWithSingleOwner'; + +export const getUserSingleOwnedRooms = function (subscribedRooms: SubscribedRoomsForUserWithDetails[]): unknown { + const roomsThatWillChangeOwner = subscribedRooms + .filter(({ shouldChangeOwner }) => shouldChangeOwner) + .map(({ rid }: { rid: string }) => rid); + const roomsThatWillBeRemoved = subscribedRooms.filter(({ shouldBeRemoved }) => shouldBeRemoved).map(({ rid }: { rid: string }) => rid); + + const roomIds = roomsThatWillBeRemoved.concat(roomsThatWillChangeOwner); + const rooms = Rooms.findByIds(roomIds, { fields: { _id: 1, name: 1, fname: 1 } }); + + const result = { + shouldBeRemoved: [] as string[], + shouldChangeOwner: [] as string[], + }; + + rooms.forEach((room: IRoom) => { + const name = room.fname || room.name; + if (roomsThatWillBeRemoved.includes(room._id)) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + result.shouldBeRemoved.push(name!); + } else { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + result.shouldChangeOwner.push(name!); + } + }); + + return result; +}; diff --git a/app/lib/server/functions/getUsernameSuggestion.js b/app/lib/server/functions/getUsernameSuggestion.ts similarity index 62% rename from app/lib/server/functions/getUsernameSuggestion.js rename to app/lib/server/functions/getUsernameSuggestion.ts index 0cc04e827801..12c617d048fc 100644 --- a/app/lib/server/functions/getUsernameSuggestion.js +++ b/app/lib/server/functions/getUsernameSuggestion.ts @@ -1,13 +1,14 @@ import limax from 'limax'; -import { Users } from '../../../models'; -import { settings } from '../../../settings'; +import { IUser } from '../../../../definition/IUser'; +import { Users } from '../../../models/server'; +import { settings } from '../../../settings/server'; -function slug(text) { +function slug(text: string): string { return limax(text, { replacement: '.' }).replace(/[^0-9a-z-_.]/g, ''); } -function usernameIsAvaliable(username) { +function usernameIsAvailable(username: string): boolean { if (username.length === 0) { return false; } @@ -19,9 +20,9 @@ function usernameIsAvaliable(username) { return !Users.findOneByUsernameIgnoringCase(username); } -const name = (username) => (settings.get('UTF8_Names_Slugify') ? slug(username) : username); +const name = (username: string): string => (settings.get('UTF8_Names_Slugify') ? slug(username) : username); -export function generateUsernameSuggestion(user) { +export function generateUsernameSuggestion(user: Pick): string | undefined { let usernames = []; if (user.name) { @@ -37,12 +38,14 @@ export function generateUsernameSuggestion(user) { } } - if (user.profile && user.profile.name) { - usernames.push(name(user.profile.name)); + if (user?.name) { + usernames.push(name(user.name)); } if (Array.isArray(user.services)) { - const services = new Set(user.services.flatMap(({ name, username, firstName, lastName }) => [name, username, firstName, lastName])); + const services = [ + ...new Set(user.services.flatMap(({ name, username, firstName, lastName }) => [name, username, firstName, lastName])), + ]; usernames.push(...services.map(name)); } @@ -58,7 +61,7 @@ export function generateUsernameSuggestion(user) { usernames = usernames.filter((e) => e); for (const item of usernames) { - if (usernameIsAvaliable(item)) { + if (usernameIsAvailable(item)) { return item; } } @@ -68,7 +71,7 @@ export function generateUsernameSuggestion(user) { let index = Users.find({ username: new RegExp(`^${usernames[0]}-[0-9]+`) }).count(); const username = ''; while (!username) { - if (usernameIsAvaliable(`${usernames[0]}-${index}`)) { + if (usernameIsAvailable(`${usernames[0]}-${index}`)) { return `${usernames[0]}-${index}`; } index++; diff --git a/app/lib/server/functions/index.js b/app/lib/server/functions/index.ts similarity index 100% rename from app/lib/server/functions/index.js rename to app/lib/server/functions/index.ts diff --git a/app/lib/server/functions/insertMessage.js b/app/lib/server/functions/insertMessage.js index 2943698ec671..13595390ca86 100644 --- a/app/lib/server/functions/insertMessage.js +++ b/app/lib/server/functions/insertMessage.js @@ -1,152 +1,20 @@ -import { Match, check } from 'meteor/check'; - -import { Markdown } from '../../../markdown/server'; -import { Messages } from '../../../models'; - -const objectMaybeIncluding = (types) => - Match.Where((value) => { - Object.keys(types).forEach((field) => { - if (value[field] != null) { - try { - check(value[field], types[field]); - } catch (error) { - error.path = field; - throw error; - } - } - }); - - return true; - }); - -const validateAttachmentsFields = (attachmentField) => { - check( - attachmentField, - objectMaybeIncluding({ - short: Boolean, - title: String, - value: Match.OneOf(String, Match.Integer, Boolean), - }), - ); - - if (typeof attachmentField.value !== 'undefined') { - attachmentField.value = String(attachmentField.value); - } -}; - -const validateAttachmentsActions = (attachmentActions) => { - check( - attachmentActions, - objectMaybeIncluding({ - type: String, - text: String, - url: String, - image_url: String, - is_webview: Boolean, - webview_height_ratio: String, - msg: String, - msg_in_chat_window: Boolean, - }), - ); -}; - -const validateAttachment = (attachment) => { - check( - attachment, - objectMaybeIncluding({ - color: String, - text: String, - ts: Match.OneOf(String, Number), - thumb_url: String, - button_alignment: String, - actions: [Match.Any], - message_link: String, - collapsed: Boolean, - author_name: String, - author_link: String, - author_icon: String, - title: String, - title_link: String, - title_link_download: Boolean, - image_url: String, - audio_url: String, - video_url: String, - fields: [Match.Any], - }), - ); - - if (attachment.fields && attachment.fields.length) { - attachment.fields.map(validateAttachmentsFields); - } - - if (attachment.actions && attachment.actions.length) { - attachment.actions.map(validateAttachmentsActions); - } -}; - -const validateBodyAttachments = (attachments) => attachments.map(validateAttachment); +import { Messages, Rooms } from '../../../models/server'; +import { validateMessage, prepareMessageObject } from './sendMessage'; +import { parseUrlsInMessage } from './parseUrlsInMessage'; export const insertMessage = function (user, message, rid, upsert = false) { if (!user || !message || !rid) { return false; } - check( - message, - objectMaybeIncluding({ - _id: String, - msg: String, - text: String, - alias: String, - emoji: String, - avatar: String, - pushm: Boolean, - attachments: [Match.Any], - }), - ); - - if (Array.isArray(message.attachments) && message.attachments.length) { - validateBodyAttachments(message.attachments); - } - - if (!message.ts) { - message.ts = new Date(); - } - const { _id, username } = user; - message.u = { - _id, - username, - }; - message.rid = rid; - - if (!Match.test(message.msg, String)) { - message.msg = ''; - } - - if (message.ts == null) { - message.ts = new Date(); - } - - if (message.parseUrls !== false) { - message.html = message.msg; - message = Markdown.code(message); - - const urls = message.html.match( - /([A-Za-z]{3,9}):\/\/([-;:&=\+\$,\w]+@{1})?([-A-Za-z0-9\.]+)+:?(\d+)?((\/[-\+=!:~%\/\.@\,\(\)\w]*)?\??([-\+=&!:;%@\/\.\,\w]+)?(?:#([^\s\)]+))?)?/g, - ); - if (urls) { - message.urls = [...new Set(urls)].map((url) => ({ url })); - } - - message = Markdown.mountTokensBack(message, false); - message.msg = message.html; - delete message.html; - delete message.tokens; - } + validateMessage(message, { _id: rid }, user); + prepareMessageObject(message, rid, user); + parseUrlsInMessage(message); if (message._id && upsert) { const { _id } = message; delete message._id; + const existingMessage = Messages.findOneById(_id); Messages.upsert( { _id, @@ -154,9 +22,13 @@ export const insertMessage = function (user, message, rid, upsert = false) { }, message, ); + if (!existingMessage) { + Rooms.incMsgCountById(rid, 1); + } message._id = _id; } else { message._id = Messages.insert(message); + Rooms.incMsgCountById(rid, 1); } return message; diff --git a/app/lib/server/functions/isTheLastMessage.js b/app/lib/server/functions/isTheLastMessage.js deleted file mode 100644 index e545c285e060..000000000000 --- a/app/lib/server/functions/isTheLastMessage.js +++ /dev/null @@ -1,4 +0,0 @@ -import { settings } from '../../../settings'; - -export const isTheLastMessage = (room, message) => - settings.get('Store_Last_Message') && (!room.lastMessage || room.lastMessage._id === message._id); diff --git a/app/lib/server/functions/isTheLastMessage.ts b/app/lib/server/functions/isTheLastMessage.ts new file mode 100644 index 000000000000..e4f76253f2c4 --- /dev/null +++ b/app/lib/server/functions/isTheLastMessage.ts @@ -0,0 +1,7 @@ +import { IMessage } from '../../../../definition/IMessage'; +import { IRoom } from '../../../../definition/IRoom'; +import { settings } from '../../../settings/server'; + +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export const isTheLastMessage = (room: IRoom, message: IMessage) => + settings.get('Store_Last_Message') && (!room.lastMessage || room.lastMessage._id === message._id); diff --git a/app/lib/server/functions/loadMessageHistory.js b/app/lib/server/functions/loadMessageHistory.ts similarity index 80% rename from app/lib/server/functions/loadMessageHistory.js rename to app/lib/server/functions/loadMessageHistory.ts index fc68c892de56..6698b4da5221 100644 --- a/app/lib/server/functions/loadMessageHistory.js +++ b/app/lib/server/functions/loadMessageHistory.ts @@ -1,9 +1,24 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ import { settings } from '../../../settings/server'; import { Messages, Rooms } from '../../../models/server'; import { normalizeMessagesForUser } from '../../../utils/server/lib/normalizeMessagesForUser'; import { getHiddenSystemMessages } from '../lib/getHiddenSystemMessages'; -export const loadMessageHistory = function loadMessageHistory({ userId, rid, end, limit = 20, ls, showThreadMessages = true }) { +export const loadMessageHistory = function loadMessageHistory({ + userId, + rid, + end, + limit = 20, + ls, + showThreadMessages = true, +}: { + userId: string; + rid: string; + end: string; + limit: number; + ls: string; + showThreadMessages: boolean; +}) { const room = Rooms.findOneById(rid, { fields: { sysMes: 1 } }); const hiddenMessageTypes = getHiddenSystemMessages(room); @@ -13,6 +28,7 @@ export const loadMessageHistory = function loadMessageHistory({ userId, rid, end ts: -1, }, limit, + fields: {}, }; if (!settings.get('Message_ShowEditedStatus')) { @@ -33,7 +49,7 @@ export const loadMessageHistory = function loadMessageHistory({ userId, rid, end const firstMessage = messages[messages.length - 1]; if ((firstMessage != null ? firstMessage.ts : undefined) > ls) { - delete options.limit; + // delete options.limit; const unreadMessages = Messages.findVisibleByRoomIdBetweenTimestampsNotContainingTypes( rid, ls, diff --git a/app/lib/server/functions/notifications/desktop.js b/app/lib/server/functions/notifications/desktop.js index 46ed746bfed3..d51043123139 100644 --- a/app/lib/server/functions/notifications/desktop.js +++ b/app/lib/server/functions/notifications/desktop.js @@ -1,7 +1,8 @@ -import { metrics } from '../../../../metrics'; -import { settings } from '../../../../settings'; -import { Notifications } from '../../../../notifications'; -import { roomTypes } from '../../../../utils'; +import { roomCoordinator } from '../../../../../server/lib/rooms/roomCoordinator'; +import { api } from '../../../../../server/sdk/api'; +import { metrics } from '../../../../metrics/server'; +import { settings } from '../../../../settings/server'; + /** * Send notification to user * @@ -13,10 +14,9 @@ import { roomTypes } from '../../../../utils'; * @param {string} notificationMessage The message text to send on notification body */ export function notifyDesktopUser({ userId, user, message, room, duration, notificationMessage }) { - const { title, text } = roomTypes.getConfig(room.t).getNotificationDetails(room, user, notificationMessage); + const { title, text } = roomCoordinator.getRoomDirectives(room.t)?.getNotificationDetails(room, user, notificationMessage, userId); - metrics.notificationsSent.inc({ notification_type: 'desktop' }); - Notifications.notifyUser(userId, 'notification', { + const payload = { title, text, duration, @@ -32,7 +32,11 @@ export function notifyDesktopUser({ userId, user, message, room, duration, notif t: message.t, }, }, - }); + }; + + metrics.notificationsSent.inc({ notification_type: 'desktop' }); + + api.broadcast('notify.desktop', userId, payload); } export function shouldNotifyDesktop({ diff --git a/app/lib/server/functions/notifications/email.js b/app/lib/server/functions/notifications/email.js index 7b395e4ae936..8126497b28af 100644 --- a/app/lib/server/functions/notifications/email.js +++ b/app/lib/server/functions/notifications/email.js @@ -5,10 +5,10 @@ import { escapeHTML } from '@rocket.chat/string-helpers'; import * as Mailer from '../../../../mailer'; import { settings } from '../../../../settings/server'; -import { roomTypes } from '../../../../utils'; import { metrics } from '../../../../metrics'; import { callbacks } from '../../../../../lib/callbacks'; import { getURL } from '../../../../utils/server'; +import { roomCoordinator } from '../../../../../server/lib/rooms/roomCoordinator'; let advice = ''; let goToMessage = ''; @@ -24,12 +24,12 @@ Meteor.startup(() => { function getEmailContent({ message, user, room }) { const lng = (user && user.language) || settings.get('Language') || 'en'; - const roomName = escapeHTML(`#${roomTypes.getRoomName(room.t, room)}`); + const roomName = escapeHTML(`#${roomCoordinator.getRoomName(room.t, room)}`); const userName = escapeHTML(settings.get('UI_Use_Real_Name') ? message.u.name || message.u.username : message.u.username); - const roomType = roomTypes.getConfig(room.t); + const roomDirectives = roomCoordinator.getRoomDirectives(room.t); - const header = TAPi18n.__(!roomType.isGroupChat(room) ? 'User_sent_a_message_to_you' : 'User_sent_a_message_on_channel', { + const header = TAPi18n.__(!roomDirectives.isGroupChat(room) ? 'User_sent_a_message_to_you' : 'User_sent_a_message_on_channel', { username: userName, channel: roomName, lng, @@ -57,7 +57,7 @@ function getEmailContent({ message, user, room }) { } if (message.file) { - const fileHeader = TAPi18n.__(!roomType.isGroupChat(room) ? 'User_uploaded_a_file_to_you' : 'User_uploaded_a_file_on_channel', { + const fileHeader = TAPi18n.__(!roomDirectives.isGroupChat(room) ? 'User_uploaded_a_file_to_you' : 'User_uploaded_a_file_on_channel', { username: userName, channel: roomName, lng, @@ -99,7 +99,9 @@ function getEmailContent({ message, user, room }) { } const getButtonUrl = (room, subscription, message) => { - const path = `${s.ltrim(roomTypes.getRelativePath(room.t, subscription), '/')}?msg=${message._id}`; + const basePath = roomCoordinator.getRouteLink(room.t, subscription).replace(Meteor.absoluteUrl(), ''); + + const path = `${s.ltrim(basePath, '/')}?msg=${message._id}`; return getURL(path, { full: true, cloud: settings.get('Offline_Message_Use_DeepLink'), @@ -119,7 +121,7 @@ export function getEmailData({ message, receiver, sender, subscription, room, em const username = settings.get('UI_Use_Real_Name') ? message.u.name || message.u.username : message.u.username; let subjectKey = 'Offline_Mention_All_Email'; - if (!roomTypes.getConfig(room.t).isGroupChat(room)) { + if (!roomCoordinator.getRoomDirectives(room.t)?.isGroupChat(room)) { subjectKey = 'Offline_DM_Email'; } else if (hasMentionToUser) { subjectKey = 'Offline_Mention_Email'; @@ -127,7 +129,7 @@ export function getEmailData({ message, receiver, sender, subscription, room, em const emailSubject = Mailer.replace(settings.get(subjectKey), { user: username, - room: roomTypes.getRoomName(room.t, room), + room: roomCoordinator.getRoomName(room.t, room), }); const content = getEmailContent({ message, diff --git a/app/lib/server/functions/notifications/mobile.js b/app/lib/server/functions/notifications/mobile.js index 016fb16558e8..e7d9752c3ed6 100644 --- a/app/lib/server/functions/notifications/mobile.js +++ b/app/lib/server/functions/notifications/mobile.js @@ -2,8 +2,8 @@ import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { settings } from '../../../../settings'; import { Subscriptions } from '../../../../models/server/raw'; +import { roomCoordinator } from '../../../../../server/lib/rooms/roomCoordinator'; import { PushNotificationSubscriptions } from '../../../../models'; -import { roomTypes } from '../../../../utils'; const CATEGORY_MESSAGE = 'MESSAGE'; const CATEGORY_MESSAGE_NOREPLY = 'MESSAGE_NOREPLY'; @@ -32,7 +32,7 @@ export async function getPushData({ receiver, shouldOmitMessage = true, }) { - const username = (settings.get('Push_show_username_room') && settings.get('UI_Use_Real_Name') && senderName) || senderUsername; + const username = settings.get('Push_show_username_room') ? (settings.get('UI_Use_Real_Name') && senderName) || senderUsername : ''; const lng = receiver.language || settings.get('Language') || 'en'; @@ -56,8 +56,8 @@ export async function getPushData({ ...(message.t === 'e2e' && { msg: message.msg }), }, roomName: - settings.get('Push_show_username_room') && roomTypes.getConfig(room.t).isGroupChat(room) - ? `#${roomTypes.getRoomName(room.t, room)}` + settings.get('Push_show_username_room') && roomCoordinator.getRoomDirectives(room.t)?.isGroupChat(room) + ? `#${roomCoordinator.getRoomName(room.t, room, userId)}` : '', username, message: messageText, @@ -68,7 +68,7 @@ export async function getPushData({ } export function getNotificationPayload({ userId, user, message, room, duration, notificationMessage }) { - const { title, text } = roomTypes.getConfig(room.t).getNotificationDetails(room, user, notificationMessage); + const { title, text } = roomCoordinator.getRoomDirectives(room.t)?.getNotificationDetails(room, user, notificationMessage); return { title, diff --git a/app/lib/server/functions/relinquishRoomOwnerships.js b/app/lib/server/functions/relinquishRoomOwnerships.ts similarity index 55% rename from app/lib/server/functions/relinquishRoomOwnerships.js rename to app/lib/server/functions/relinquishRoomOwnerships.ts index 9757ff0a08d2..9c68ac5f9c5d 100644 --- a/app/lib/server/functions/relinquishRoomOwnerships.js +++ b/app/lib/server/functions/relinquishRoomOwnerships.ts @@ -1,26 +1,31 @@ import { FileUpload } from '../../../file-upload/server'; import { Subscriptions, Messages, Rooms } from '../../../models/server'; import { Roles } from '../../../models/server/raw'; +import { SubscribedRoomsForUserWithDetails } from './getRoomsWithSingleOwner'; -const bulkRoomCleanUp = (rids) => { +const bulkRoomCleanUp = (rids: string[]): unknown => { // no bulk deletion for files rids.forEach((rid) => FileUpload.removeFilesByRoomId(rid)); return Promise.await(Promise.all([Subscriptions.removeByRoomIds(rids), Messages.removeByRoomIds(rids), Rooms.removeByIds(rids)])); }; -export const relinquishRoomOwnerships = async function (userId, subscribedRooms, removeDirectMessages = true) { +export const relinquishRoomOwnerships = async function ( + userId: string, + subscribedRooms: SubscribedRoomsForUserWithDetails[], + removeDirectMessages = true, +): Promise { // change owners const changeOwner = subscribedRooms.filter(({ shouldChangeOwner }) => shouldChangeOwner); for await (const { newOwner, rid } of changeOwner) { - await Roles.addUserRoles(newOwner, ['owner'], rid); + newOwner && (await Roles.addUserRoles(newOwner, ['owner'], rid)); } - const roomIdsToRemove = subscribedRooms.filter(({ shouldBeRemoved }) => shouldBeRemoved).map(({ rid }) => rid); + const roomIdsToRemove: string[] = subscribedRooms.filter(({ shouldBeRemoved }) => shouldBeRemoved).map(({ rid }) => rid); if (removeDirectMessages) { - Rooms.find1On1ByUserId(userId, { fields: { _id: 1 } }).forEach(({ _id }) => roomIdsToRemove.push(_id)); + Rooms.find1On1ByUserId(userId, { fields: { _id: 1 } }).forEach(({ _id }: { _id: string }) => roomIdsToRemove.push(_id)); } bulkRoomCleanUp(roomIdsToRemove); diff --git a/app/lib/server/functions/removeUserFromRoom.js b/app/lib/server/functions/removeUserFromRoom.js deleted file mode 100644 index 32eea62a5215..000000000000 --- a/app/lib/server/functions/removeUserFromRoom.js +++ /dev/null @@ -1,59 +0,0 @@ -import { AppsEngineException } from '@rocket.chat/apps-engine/definition/exceptions'; -import { Meteor } from 'meteor/meteor'; - -import { Rooms, Messages, Subscriptions } from '../../../models'; -import { AppEvents, Apps } from '../../../apps/server'; -import { callbacks } from '../../../../lib/callbacks'; -import { Team } from '../../../../server/sdk'; - -export const removeUserFromRoom = function (rid, user, options = {}) { - const room = Rooms.findOneById(rid); - - if (room) { - try { - Promise.await(Apps.triggerEvent(AppEvents.IPreRoomUserLeave, room, user)); - } catch (error) { - if (error instanceof AppsEngineException) { - throw new Meteor.Error('error-app-prevented', error.message); - } - - throw error; - } - - callbacks.run('beforeLeaveRoom', user, room); - - const subscription = Subscriptions.findOneByRoomIdAndUserId(rid, user._id, { - fields: { _id: 1 }, - }); - - if (subscription) { - const removedUser = user; - if (options.byUser) { - Messages.createUserRemovedWithRoomIdAndUser(rid, user, { - u: options.byUser, - }); - } else if (room.teamMain) { - Messages.createUserLeaveTeamWithRoomIdAndUser(rid, removedUser); - } else { - Messages.createUserLeaveWithRoomIdAndUser(rid, removedUser); - } - } - - if (room.t === 'l') { - Messages.createCommandWithRoomIdAndUser('survey', rid, user); - } - - Subscriptions.removeByRoomIdAndUserId(rid, user._id); - - if (room.teamId && room.teamMain) { - Promise.await(Team.removeMember(room.teamId, user._id)); - } - - Meteor.defer(function () { - // TODO: CACHE: maybe a queue? - callbacks.run('afterLeaveRoom', user, room); - - Promise.await(Apps.triggerEvent(AppEvents.IPostRoomUserLeave, room, user)); - }); - } -}; diff --git a/app/lib/server/functions/removeUserFromRoom.ts b/app/lib/server/functions/removeUserFromRoom.ts new file mode 100644 index 000000000000..3349ea3a38ae --- /dev/null +++ b/app/lib/server/functions/removeUserFromRoom.ts @@ -0,0 +1,71 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { AppsEngineException } from '@rocket.chat/apps-engine/definition/exceptions'; +import { Meteor } from 'meteor/meteor'; + +import { Rooms, Messages, Subscriptions } from '../../../models/server'; +import { AppEvents, Apps } from '../../../apps/server'; +import { callbacks } from '../../../../lib/callbacks'; +import { Team } from '../../../../server/sdk'; +import { IUser } from '../../../../definition/IUser'; + +export const removeUserFromRoom = async function ( + rid: string, + user: IUser, + options?: { byUser: Pick }, +): Promise { + const room = Rooms.findOneById(rid); + + if (!room) { + return; + } + + try { + await Apps.triggerEvent(AppEvents.IPreRoomUserLeave, room, user); + } catch (error) { + if (error instanceof AppsEngineException) { + throw new Meteor.Error('error-app-prevented', error.message); + } + + throw error; + } + + callbacks.run('beforeLeaveRoom', user, room); + + const subscription = Subscriptions.findOneByRoomIdAndUserId(rid, user._id, { + fields: { _id: 1 }, + }); + + if (subscription) { + const removedUser = user; + if (options?.byUser) { + const extraData = { + u: options.byUser, + }; + + if (room.teamMain) { + Messages.createUserRemovedFromTeamWithRoomIdAndUser(rid, user, extraData); + } else { + Messages.createUserRemovedWithRoomIdAndUser(rid, user, extraData); + } + } else if (room.teamMain) { + Messages.createUserLeaveTeamWithRoomIdAndUser(rid, removedUser); + } else { + Messages.createUserLeaveWithRoomIdAndUser(rid, removedUser); + } + } + + if (room.t === 'l') { + Messages.createCommandWithRoomIdAndUser('survey', rid, user); + } + + Subscriptions.removeByRoomIdAndUserId(rid, user._id); + + if (room.teamId && room.teamMain) { + await Team.removeMember(room.teamId, user._id); + } + + // TODO: CACHE: maybe a queue? + callbacks.run('afterLeaveRoom', user, room); + + await Apps.triggerEvent(AppEvents.IPostRoomUserLeave, room, user); +}; diff --git a/app/lib/server/functions/saveCustomFields.js b/app/lib/server/functions/saveCustomFields.ts similarity index 66% rename from app/lib/server/functions/saveCustomFields.js rename to app/lib/server/functions/saveCustomFields.ts index 1fde2a53b849..228b3cb14658 100644 --- a/app/lib/server/functions/saveCustomFields.js +++ b/app/lib/server/functions/saveCustomFields.ts @@ -1,9 +1,9 @@ import s from 'underscore.string'; -import { settings } from '../../../settings'; +import { settings } from '../../../settings/server'; import { validateCustomFields, saveCustomFieldsWithoutValidation } from '.'; -export const saveCustomFields = function (userId, formData) { +export const saveCustomFields = function (userId: string, formData: unknown): void { if (s.trim(settings.get('Accounts_CustomFields')) !== '') { validateCustomFields(formData); return saveCustomFieldsWithoutValidation(userId, formData); diff --git a/app/lib/server/functions/saveUser.js b/app/lib/server/functions/saveUser.js index e85f9ac8d100..736221d2e3af 100644 --- a/app/lib/server/functions/saveUser.js +++ b/app/lib/server/functions/saveUser.js @@ -71,7 +71,7 @@ function validateUserData(userId, userData) { } if (userData.roles && _.difference(userData.roles, existingRoles).length > 0) { - throw new Meteor.Error('error-action-not-allowed', 'The field Roles consist invalid role name', { + throw new Meteor.Error('error-action-not-allowed', 'The field Roles consist invalid role id', { method: 'insertOrUpdateUser', action: 'Assign_role', }); @@ -207,7 +207,7 @@ export function validateUserEditing(userId, userData) { } if ( - user.emails[0] && + user.emails?.[0] && isEditingField(user.emails[0].address, userData.email) && !settings.get('Accounts_AllowEmailChange') && (!canEditOtherUserInfo || editingMyself) diff --git a/app/lib/server/functions/saveUserIdentity.js b/app/lib/server/functions/saveUserIdentity.ts similarity index 87% rename from app/lib/server/functions/saveUserIdentity.js rename to app/lib/server/functions/saveUserIdentity.ts index a3e38ca07ea5..de227153c1bb 100644 --- a/app/lib/server/functions/saveUserIdentity.js +++ b/app/lib/server/functions/saveUserIdentity.ts @@ -1,15 +1,26 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ import { _setUsername } from './setUsername'; import { _setRealName } from './setRealName'; import { Messages, Rooms, Subscriptions, LivechatDepartmentAgents, Users } from '../../../models/server'; import { FileUpload } from '../../../file-upload/server'; import { updateGroupDMsName } from './updateGroupDMsName'; import { validateName } from './validateName'; +import { IMessage } from '../../../../definition/IMessage'; /** * * @param {object} changes changes to the user */ -export function saveUserIdentity({ _id, name: rawName, username: rawUsername }) { + +export function saveUserIdentity({ + _id, + name: rawName, + username: rawUsername, +}: { _id: string } & ( + | { name: string; username: string } + | { name: string; username?: undefined } + | { username: string; name?: undefined } +)) { if (!_id) { return false; } @@ -46,7 +57,7 @@ export function saveUserIdentity({ _id, name: rawName, username: rawUsername }) if (usernameChanged && typeof rawUsername !== 'undefined') { Messages.updateAllUsernamesByUserId(user._id, username); Messages.updateUsernameOfEditByUserId(user._id, username); - Messages.findByMention(previousUsername).forEach(function (msg) { + Messages.findByMention(previousUsername).forEach(function (msg: IMessage) { const updatedMsg = msg.msg.replace(new RegExp(`@${previousUsername}`, 'ig'), `@${username}`); return Messages.updateUsernameAndMessageOfMentionByIdAndOldUsername(msg._id, previousUsername, username, updatedMsg); }); diff --git a/app/lib/server/functions/sendMessage.js b/app/lib/server/functions/sendMessage.js index a671b4896296..cc0ce3f897e1 100644 --- a/app/lib/server/functions/sendMessage.js +++ b/app/lib/server/functions/sendMessage.js @@ -22,7 +22,7 @@ const { DISABLE_MESSAGE_PARSER = 'false' } = process.env; * is going to be rendered in the href attribute of a * link. */ -const ValidFullURLParam = Match.Where((value) => { +const validFullURLParam = Match.Where((value) => { check(value, String); if (!isURL(value) && !value.startsWith(FileUpload.getPath())) { @@ -36,7 +36,7 @@ const ValidFullURLParam = Match.Where((value) => { return true; }); -const ValidPartialURLParam = Match.Where((value) => { +const validPartialURLParam = Match.Where((value) => { check(value, String); if (!isRelativeURL(value) && !isURL(value) && !value.startsWith(FileUpload.getPath())) { @@ -87,8 +87,8 @@ const validateAttachmentsActions = (attachmentActions) => { objectMaybeIncluding({ type: String, text: String, - url: ValidFullURLParam, - image_url: ValidFullURLParam, + url: validFullURLParam, + image_url: validFullURLParam, is_webview: Boolean, webview_height_ratio: String, msg: String, @@ -104,26 +104,26 @@ const validateAttachment = (attachment) => { color: String, text: String, ts: Match.OneOf(String, Number), - thumb_url: ValidFullURLParam, + thumb_url: validFullURLParam, button_alignment: String, actions: [Match.Any], - message_link: ValidFullURLParam, + message_link: validFullURLParam, collapsed: Boolean, author_name: String, - author_link: ValidFullURLParam, - author_icon: ValidFullURLParam, + author_link: validFullURLParam, + author_icon: validFullURLParam, title: String, - title_link: ValidFullURLParam, + title_link: validFullURLParam, title_link_download: Boolean, image_dimensions: Object, - image_url: ValidFullURLParam, + image_url: validFullURLParam, image_preview: String, image_type: String, image_size: Number, - audio_url: ValidFullURLParam, + audio_url: validFullURLParam, audio_type: String, audio_size: Number, - video_url: ValidFullURLParam, + video_url: validFullURLParam, video_type: String, video_size: Number, fields: [Match.Any], @@ -141,7 +141,7 @@ const validateAttachment = (attachment) => { const validateBodyAttachments = (attachments) => attachments.map(validateAttachment); -const validateMessage = (message, room, user) => { +export const validateMessage = (message, room, user) => { check( message, objectMaybeIncluding({ @@ -152,9 +152,10 @@ const validateMessage = (message, room, user) => { emoji: String, tmid: String, tshow: Boolean, - avatar: ValidPartialURLParam, + avatar: validPartialURLParam, attachments: [Match.Any], blocks: [Match.Any], + pushm: Boolean, }), ); @@ -171,13 +172,7 @@ const validateMessage = (message, room, user) => { } }; -export const sendMessage = function (user, message, room, upsert = false) { - if (!user || !message || !room._id) { - return false; - } - - validateMessage(message, room, user); - +export const prepareMessageObject = function (message, rid, user) { if (!message.ts) { message.ts = new Date(); } @@ -192,7 +187,7 @@ export const sendMessage = function (user, message, room, upsert = false) { username, name, }; - message.rid = room._id; + message.rid = rid; if (!Match.test(message.msg, String)) { message.msg = ''; @@ -201,6 +196,23 @@ export const sendMessage = function (user, message, room, upsert = false) { if (message.ts == null) { message.ts = new Date(); } +}; + +/** + * Clean up the message object before saving on db + * @param {IMessage} message + */ +function cleanupMessageObject(message) { + ['customClass'].forEach((field) => delete message[field]); +} + +export const sendMessage = function (user, message, room, upsert = false) { + if (!user || !message || !room._id) { + return false; + } + + validateMessage(message, room, user); + prepareMessageObject(message, room._id, user); if (settings.get('Message_Read_Receipt_Enabled')) { message.unread = true; @@ -229,6 +241,8 @@ export const sendMessage = function (user, message, room, upsert = false) { } } + cleanupMessageObject(message); + parseUrlsInMessage(message); message = callbacks.run('beforeSaveMessage', message, room); diff --git a/app/lib/server/functions/setEmail.js b/app/lib/server/functions/setEmail.ts similarity index 74% rename from app/lib/server/functions/setEmail.js rename to app/lib/server/functions/setEmail.ts index 609b988f1a15..8ee0eef21ca6 100644 --- a/app/lib/server/functions/setEmail.js +++ b/app/lib/server/functions/setEmail.ts @@ -1,12 +1,13 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ import { Meteor } from 'meteor/meteor'; import s from 'underscore.string'; import { escapeHTML } from '@rocket.chat/string-helpers'; -import { Users } from '../../../models'; -import { hasPermission } from '../../../authorization'; +import { Users } from '../../../models/server'; +import { hasPermission } from '../../../authorization/server'; import { RateLimiter, validateEmailDomain } from '../lib'; import * as Mailer from '../../../mailer'; -import { settings } from '../../../settings'; +import { settings } from '../../../settings/server'; import { checkEmailAvailability } from '.'; let html = ''; @@ -16,11 +17,11 @@ Meteor.startup(() => { }); }); -const _sendEmailChangeNotification = function (to, newEmail) { - const subject = settings.get('Email_Changed_Email_Subject'); +const _sendEmailChangeNotification = function (to: string, newEmail: string) { + const subject = String(settings.get('Email_Changed_Email_Subject')); const email = { to, - from: settings.get('From_Email'), + from: String(settings.get('From_Email')), subject, html, data: { @@ -30,7 +31,7 @@ const _sendEmailChangeNotification = function (to, newEmail) { try { Mailer.send(email); - } catch (error) { + } catch (error: any) { throw new Meteor.Error('error-email-send-failed', `Error trying to send email: ${error.message}`, { function: 'setEmail', message: error.message, @@ -38,7 +39,7 @@ const _sendEmailChangeNotification = function (to, newEmail) { } }; -const _setEmail = function (userId, email, shouldSendVerificationEmail = true) { +const _setEmail = function (userId: string, email: string, shouldSendVerificationEmail = true) { email = s.trim(email); if (!userId) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { function: '_setEmail' }); @@ -82,6 +83,7 @@ const _setEmail = function (userId, email, shouldSendVerificationEmail = true) { export const setEmail = RateLimiter.limitFunction(_setEmail, 1, 60000, { 0() { - return !Meteor.userId() || !hasPermission(Meteor.userId(), 'edit-other-user-info'); + const userId = Meteor.userId(); + return !userId || !hasPermission(userId, 'edit-other-user-info'); }, // Administrators have permission to change others emails, so don't limit those }); diff --git a/app/lib/server/functions/setRealName.js b/app/lib/server/functions/setRealName.ts similarity index 69% rename from app/lib/server/functions/setRealName.js rename to app/lib/server/functions/setRealName.ts index 38f19c0ab7cb..c8f6f53dfb8e 100644 --- a/app/lib/server/functions/setRealName.js +++ b/app/lib/server/functions/setRealName.ts @@ -2,12 +2,13 @@ import { Meteor } from 'meteor/meteor'; import s from 'underscore.string'; import { Users } from '../../../models/server'; -import { settings } from '../../../settings'; -import { hasPermission } from '../../../authorization'; +import { settings } from '../../../settings/server'; +import { hasPermission } from '../../../authorization/server'; import { RateLimiter } from '../lib'; import { api } from '../../../../server/sdk/api'; +import { IUser } from '../../../../definition/IUser'; -export const _setRealName = function (userId, name, fullUser) { +export const _setRealName = function (userId: string, name: string, fullUser: IUser): unknown { name = s.trim(name); if (!userId || (settings.get('Accounts_RequireNameForSignUp') && !name)) { @@ -17,7 +18,7 @@ export const _setRealName = function (userId, name, fullUser) { const user = fullUser || Users.findOneById(userId); // User already has desired name, return - if (s.trim(user.name) === name) { + if (user.name && s.trim(user.name) === name) { return user; } @@ -42,6 +43,7 @@ export const _setRealName = function (userId, name, fullUser) { export const setRealName = RateLimiter.limitFunction(_setRealName, 1, 60000, { 0() { - return !Meteor.userId() || !hasPermission(Meteor.userId(), 'edit-other-user-info'); + const userId = Meteor.userId(); + return !userId || !hasPermission(userId, 'edit-other-user-info'); }, // Administrators have permission to change others names, so don't limit those }); diff --git a/app/lib/server/functions/setRoomAvatar.js b/app/lib/server/functions/setRoomAvatar.ts similarity index 80% rename from app/lib/server/functions/setRoomAvatar.js rename to app/lib/server/functions/setRoomAvatar.ts index 474797aaa204..d33c766485b1 100644 --- a/app/lib/server/functions/setRoomAvatar.js +++ b/app/lib/server/functions/setRoomAvatar.ts @@ -1,12 +1,13 @@ import { Meteor } from 'meteor/meteor'; import { RocketChatFile } from '../../../file'; -import { FileUpload } from '../../../file-upload'; +import { FileUpload } from '../../../file-upload/server'; import { Rooms, Messages } from '../../../models/server'; import { Avatars } from '../../../models/server/raw'; import { api } from '../../../../server/sdk/api'; +import { IUser } from '../../../../definition/IUser'; -export const setRoomAvatar = async function (rid, dataURI, user) { +export const setRoomAvatar = async function (rid: string, dataURI: string, user: IUser): Promise { const fileStore = FileUpload.getStore('Avatars'); const current = await Avatars.findOneByRoomId(rid); @@ -30,7 +31,7 @@ export const setRoomAvatar = async function (rid, dataURI, user) { uid: user._id, }; - fileStore.insert(file, buffer, (err, result) => { + fileStore.insert(file, buffer, (err: unknown, result: { etag: string }) => { if (err) { throw err; } diff --git a/app/lib/server/functions/setStatusText.js b/app/lib/server/functions/setStatusText.ts similarity index 83% rename from app/lib/server/functions/setStatusText.js rename to app/lib/server/functions/setStatusText.ts index b584b0fdeb48..ee8e533a81a4 100644 --- a/app/lib/server/functions/setStatusText.js +++ b/app/lib/server/functions/setStatusText.ts @@ -7,7 +7,7 @@ import { hasPermission } from '../../../authorization/server'; import { RateLimiter } from '../lib'; import { api } from '../../../../server/sdk/api'; -export const _setStatusTextPromise = async function (userId, statusText) { +export const _setStatusTextPromise = async function (userId: string, statusText: string): Promise { if (!userId) { return false; } @@ -34,7 +34,7 @@ export const _setStatusTextPromise = async function (userId, statusText) { return true; }; -export const _setStatusText = function (userId, statusText) { +export const _setStatusText = function (userId: any, statusText: string): unknown { statusText = s.trim(statusText); if (statusText.length > 120) { statusText = statusText.substr(0, 120); @@ -66,6 +66,7 @@ export const _setStatusText = function (userId, statusText) { export const setStatusText = RateLimiter.limitFunction(_setStatusText, 5, 60000, { 0() { // Administrators have permission to change others status, so don't limit those - return !Meteor.userId() || !hasPermission(Meteor.userId(), 'edit-other-user-info'); + const userId = Meteor.userId(); + return !userId || !hasPermission(userId, 'edit-other-user-info'); }, }); diff --git a/app/lib/server/functions/setUserActiveStatus.js b/app/lib/server/functions/setUserActiveStatus.ts similarity index 69% rename from app/lib/server/functions/setUserActiveStatus.js rename to app/lib/server/functions/setUserActiveStatus.ts index 45ab2aab0c31..69fcd6d5bc32 100644 --- a/app/lib/server/functions/setUserActiveStatus.js +++ b/app/lib/server/functions/setUserActiveStatus.ts @@ -3,26 +3,28 @@ import { check } from 'meteor/check'; import { Accounts } from 'meteor/accounts-base'; import * as Mailer from '../../../mailer'; -import { Users, Subscriptions, Rooms } from '../../../models'; -import { settings } from '../../../settings'; +import { Users, Subscriptions, Rooms } from '../../../models/server'; +import { settings } from '../../../settings/server'; import { callbacks } from '../../../../lib/callbacks'; import { relinquishRoomOwnerships } from './relinquishRoomOwnerships'; import { closeOmnichannelConversations } from './closeOmnichannelConversations'; import { shouldRemoveOrChangeOwner, getSubscribedRoomsForUserWithDetails } from './getRoomsWithSingleOwner'; import { getUserSingleOwnedRooms } from './getUserSingleOwnedRooms'; +import { IUser, IUserEmail } from '../../../../definition/IUser'; +import { IDirectMessageRoom } from '../../../../definition/IRoom'; -function reactivateDirectConversations(userId) { +function reactivateDirectConversations(userId: string): void { // since both users can be deactivated at the same time, we should just reactivate rooms if both users are active // for that, we need to fetch the direct messages, fetch the users involved and then the ids of rooms we can reactivate const directConversations = Rooms.getDirectConversationsByUserId(userId, { projection: { _id: 1, uids: 1 }, }).fetch(); - const userIds = directConversations.reduce((acc, r) => acc.push(...r.uids) && acc, []); + const userIds = directConversations.reduce((acc: string[], r: IDirectMessageRoom) => acc.push(...r.uids) && acc, []); const uniqueUserIds = [...new Set(userIds)]; const activeUsers = Users.findActiveByUserIds(uniqueUserIds, { projection: { _id: 1 } }).fetch(); - const activeUserIds = activeUsers.map((u) => u._id); - const roomsToReactivate = directConversations.reduce((acc, room) => { - const otherUserId = room.uids.find((u) => u !== userId); + const activeUserIds = activeUsers.map((u: IUser) => u._id); + const roomsToReactivate = directConversations.reduce((acc: string[], room: IDirectMessageRoom) => { + const otherUserId = room.uids.find((u: string) => u !== userId); if (activeUserIds.includes(otherUserId)) { acc.push(room._id); } @@ -32,7 +34,7 @@ function reactivateDirectConversations(userId) { Rooms.setDmReadOnlyByUserId(userId, roomsToReactivate, false, false); } -export function setUserActiveStatus(userId, active, confirmRelinquish = false) { +export function setUserActiveStatus(userId: string, active: boolean, confirmRelinquish = false): boolean | undefined { check(userId, String); check(active, Boolean); @@ -59,7 +61,7 @@ export function setUserActiveStatus(userId, active, confirmRelinquish = false) { const livechatSubscribedRooms = subscribedRooms.filter(({ t }) => t === 'l'); if (shouldRemoveOrChangeOwner(chatSubscribedRooms) && !confirmRelinquish) { - const rooms = getUserSingleOwnedRooms(chatSubscribedRooms); + const rooms = getUserSingleOwnedRooms(chatSubscribedRooms as []); throw new Meteor.Error('user-last-owner', '', rooms); } @@ -99,17 +101,23 @@ export function setUserActiveStatus(userId, active, confirmRelinquish = false) { return true; } - const destinations = Array.isArray(user.emails) && user.emails.map((email) => `${user.name || user.username}<${email.address}>`); + const destinations = + Array.isArray(user.emails) && user.emails.map((email: IUserEmail) => `${user.name || user.username}<${email.address}>`); + type UserActivated = { + subject: (params: { active: boolean }) => string; + html: (params: { active: boolean; name: string; username: string }) => string; + }; + const { subject, html } = (Accounts.emailTemplates as unknown as { userActivated: UserActivated }).userActivated; const email = { - to: destinations, - from: settings.get('From_Email'), - subject: Accounts.emailTemplates.userActivated.subject({ active }), - html: Accounts.emailTemplates.userActivated.html({ + to: String(destinations), + from: String(settings.get('From_Email')), + subject: subject({ active } as any), + html: html({ active, name: user.name, username: user.username, - }), + } as any), }; Mailer.sendNoWrap(email); diff --git a/app/lib/server/functions/setUserAvatar.ts b/app/lib/server/functions/setUserAvatar.ts index c882c727999a..5e0db5aedd65 100644 --- a/app/lib/server/functions/setUserAvatar.ts +++ b/app/lib/server/functions/setUserAvatar.ts @@ -1,11 +1,11 @@ import { Meteor } from 'meteor/meteor'; -import { fetch } from 'meteor/fetch'; import { RocketChatFile } from '../../../file/server'; import { FileUpload } from '../../../file-upload/server'; import { Users } from '../../../models/server'; import { SystemLogger } from '../../../../server/lib/logger/system'; import { api } from '../../../../server/sdk/api'; +import { fetch } from '../../../../server/lib/http/fetch'; import { IUser } from '../../../../definition/IUser'; export const setUserAvatar = function ( diff --git a/app/lib/server/functions/setUsername.js b/app/lib/server/functions/setUsername.ts similarity index 79% rename from app/lib/server/functions/setUsername.js rename to app/lib/server/functions/setUsername.ts index d5e18af036c6..bbfdb333a88e 100644 --- a/app/lib/server/functions/setUsername.js +++ b/app/lib/server/functions/setUsername.ts @@ -5,15 +5,16 @@ import { Accounts } from 'meteor/accounts-base'; import { settings } from '../../../settings/server'; import { Users } from '../../../models/server'; import { Invites } from '../../../models/server/raw'; -import { hasPermission } from '../../../authorization'; +import { hasPermission } from '../../../authorization/server'; import { RateLimiter } from '../lib'; import { addUserToRoom } from './addUserToRoom'; import { api } from '../../../../server/sdk/api'; import { checkUsernameAvailability, setUserAvatar } from '.'; import { getAvatarSuggestionForUser } from './getAvatarSuggestionForUser'; import { SystemLogger } from '../../../../server/lib/logger/system'; +import { IUser } from '../../../../definition/IUser'; -export const _setUsername = function (userId, u, fullUser) { +export const _setUsername = function (userId: string, u: string, fullUser: IUser): unknown { const username = s.trim(u); if (!userId || !username) { return false; @@ -46,19 +47,20 @@ export const _setUsername = function (userId, u, fullUser) { Accounts.sendEnrollmentEmail(user._id); }); } - } catch (e) { + } catch (e: any) { SystemLogger.error(e); } // Set new username* Users.setUsername(user._id, username); user.username = username; if (!previousUsername && settings.get('Accounts_SetDefaultAvatar') === true) { - const avatarSuggestions = Promise.await(getAvatarSuggestionForUser(user)); + const avatarSuggestions = Promise.await(getAvatarSuggestionForUser(user)) as []; let gravatar; Object.keys(avatarSuggestions).some((service) => { - const avatarData = avatarSuggestions[service]; + const avatarData = avatarSuggestions[+service]; if (service !== 'gravatar') { - setUserAvatar(user, avatarData.blob, avatarData.contentType, service); + // eslint-disable-next-line dot-notation + setUserAvatar(user, avatarData['blob'], avatarData['contentType'], service); gravatar = null; return true; } @@ -66,14 +68,15 @@ export const _setUsername = function (userId, u, fullUser) { return false; }); if (gravatar != null) { - setUserAvatar(user, gravatar.blob, gravatar.contentType, 'gravatar'); + // eslint-disable-next-line dot-notation + setUserAvatar(user, gravatar['blob'], gravatar['contentType'], 'gravatar'); } } // If it's the first username and the user has an invite Token, then join the invite room if (!previousUsername && user.inviteToken) { const inviteData = Promise.await(Invites.findOneById(user.inviteToken)); - if (inviteData && inviteData.rid) { + if (inviteData?.rid) { addUserToRoom(inviteData.rid, user); } } @@ -89,6 +92,7 @@ export const _setUsername = function (userId, u, fullUser) { export const setUsername = RateLimiter.limitFunction(_setUsername, 1, 60000, { 0() { - return !Meteor.userId() || !hasPermission(Meteor.userId(), 'edit-other-user-info'); + const userId = Meteor.userId(); + return !userId || !hasPermission(userId, 'edit-other-user-info'); }, }); diff --git a/app/lib/server/functions/unarchiveRoom.js b/app/lib/server/functions/unarchiveRoom.js deleted file mode 100644 index 78e24c5ccebd..000000000000 --- a/app/lib/server/functions/unarchiveRoom.js +++ /dev/null @@ -1,6 +0,0 @@ -import { Rooms, Subscriptions } from '../../../models'; - -export const unarchiveRoom = function (rid) { - Rooms.unarchiveById(rid); - Subscriptions.unarchiveByRoomId(rid); -}; diff --git a/app/lib/server/functions/unarchiveRoom.ts b/app/lib/server/functions/unarchiveRoom.ts new file mode 100644 index 000000000000..5f1ab1415f77 --- /dev/null +++ b/app/lib/server/functions/unarchiveRoom.ts @@ -0,0 +1,9 @@ +import { Meteor } from 'meteor/meteor'; + +import { Rooms, Messages, Subscriptions } from '../../../models/server'; + +export const unarchiveRoom = function (rid: string): void { + Rooms.unarchiveById(rid); + Subscriptions.unarchiveByRoomId(rid); + Messages.createRoomUnarchivedByRoomIdAndUser(rid, Meteor.user()); +}; diff --git a/app/lib/server/functions/updateGroupDMsName.js b/app/lib/server/functions/updateGroupDMsName.js deleted file mode 100644 index 780ea74cfbae..000000000000 --- a/app/lib/server/functions/updateGroupDMsName.js +++ /dev/null @@ -1,54 +0,0 @@ -import { Rooms, Subscriptions, Users } from '../../../models/server'; - -const getFname = (members) => members.map(({ name, username }) => name || username).join(', '); -const getName = (members) => members.map(({ username }) => username).join(','); - -function getUsersWhoAreInTheSameGroupDMsAs(user) { - // add all users to single array so we can fetch details from them all at once - const rooms = Rooms.findGroupDMsByUids(user._id, { fields: { uids: 1 } }); - if (rooms.count() === 0) { - return; - } - - const userIds = new Set(); - const users = new Map(); - - rooms.forEach((room) => room.uids.forEach((uid) => uid !== user._id && userIds.add(uid))); - - Users.findByIds([...userIds], { fields: { username: 1, name: 1 } }).forEach((user) => users.set(user._id, user)); - - return users; -} - -function sortUsersAlphabetically(u1, u2) { - return (u1.name || u1.username).localeCompare(u2.name || u2.username); -} - -export const updateGroupDMsName = (userThatChangedName) => { - if (!userThatChangedName.username) { - return; - } - - const users = getUsersWhoAreInTheSameGroupDMsAs(userThatChangedName); - if (!users) { - return; - } - - users.set(userThatChangedName._id, userThatChangedName); - - const rooms = Rooms.findGroupDMsByUids(userThatChangedName._id, { fields: { uids: 1 } }); - - const getMembers = (uids) => uids.map((uid) => users.get(uid)).filter(Boolean); - - // loop rooms to update the subcriptions from them all - rooms.forEach((room) => { - const members = getMembers(room.uids); - const sortedMembers = members.sort(sortUsersAlphabetically); - - const subs = Subscriptions.findByRoomId(room._id, { fields: { '_id': 1, 'u._id': 1 } }); - subs.forEach((sub) => { - const otherMembers = sortedMembers.filter(({ _id }) => _id !== sub.u._id); - Subscriptions.updateNameAndFnameById(sub._id, getName(otherMembers), getFname(otherMembers)); - }); - }); -}; diff --git a/app/lib/server/functions/updateGroupDMsName.ts b/app/lib/server/functions/updateGroupDMsName.ts new file mode 100644 index 000000000000..56c36cabc8c7 --- /dev/null +++ b/app/lib/server/functions/updateGroupDMsName.ts @@ -0,0 +1,59 @@ +import { IDirectMessageRoom } from '../../../../definition/IRoom'; +import { IUser } from '../../../../definition/IUser'; +import { Rooms, Subscriptions, Users } from '../../../models/server'; +import { ISubscription } from '../../../../definition/ISubscription'; + +const getFname = (members: IUser[]): string => members.map(({ name, username }) => name || username).join(', '); +const getName = (members: IUser[]): string => members.map(({ username }) => username).join(','); + +function getUsersWhoAreInTheSameGroupDMsAs(user: IUser): unknown { + // add all users to single array so we can fetch details from them all at once + const rooms = Rooms.findGroupDMsByUids(user._id, { fields: { uids: 1 } }); + if (rooms.count() === 0) { + return; + } + + const userIds = new Set(); + const users = new Map(); + + rooms.forEach((room: IDirectMessageRoom) => room.uids.forEach((uid) => uid !== user._id && userIds.add(uid))); + + Users.findByIds([...userIds], { fields: { username: 1, name: 1 } }).forEach((user: IUser) => users.set(user._id, user)); + + return users; +} + +function sortUsersAlphabetically(u1: IUser, u2: IUser): number { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return (u1.name! || u1.username!).localeCompare(u2.name! || u2.username!); +} + +export const updateGroupDMsName = (userThatChangedName: IUser): void => { + if (!userThatChangedName.username) { + return; + } + + const users: any = getUsersWhoAreInTheSameGroupDMsAs(userThatChangedName); + if (!users) { + return; + } + + users.set(userThatChangedName._id, userThatChangedName); + + const rooms = Rooms.findGroupDMsByUids(userThatChangedName._id, { fields: { uids: 1 } }); + + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type + const getMembers = (uids: string[]) => uids.map((uid) => users.get(uid)).filter(Boolean); + + // loop rooms to update the subcriptions from them all + rooms.forEach((room: IDirectMessageRoom) => { + const members = getMembers(room.uids); + const sortedMembers = members.sort(sortUsersAlphabetically); + + const subs = Subscriptions.findByRoomId(room._id, { fields: { '_id': 1, 'u._id': 1 } }); + subs.forEach((sub: ISubscription) => { + const otherMembers = sortedMembers.filter(({ _id }) => _id !== sub.u._id); + Subscriptions.updateNameAndFnameById(sub._id, getName(otherMembers), getFname(otherMembers)); + }); + }); +}; diff --git a/app/lib/server/functions/updateMessage.js b/app/lib/server/functions/updateMessage.ts similarity index 59% rename from app/lib/server/functions/updateMessage.js rename to app/lib/server/functions/updateMessage.ts index 5da97ed0e1ba..bbc94b07481c 100644 --- a/app/lib/server/functions/updateMessage.js +++ b/app/lib/server/functions/updateMessage.ts @@ -7,10 +7,12 @@ import { callbacks } from '../../../../lib/callbacks'; import { SystemLogger } from '../../../../server/lib/logger/system'; import { Apps } from '../../../apps/server'; import { parseUrlsInMessage } from './parseUrlsInMessage'; +import { IMessage, IMessageEdited } from '../../../../definition/IMessage'; +import { IUser } from '../../../../definition/IUser'; const { DISABLE_MESSAGE_PARSER = 'false' } = process.env; -export const updateMessage = function (message, user, originalMessage) { +export const updateMessage = function (message: IMessage, user: IUser, originalMessage?: IMessage): void { if (!originalMessage) { originalMessage = Messages.findOneById(message._id); } @@ -19,14 +21,14 @@ export const updateMessage = function (message, user, originalMessage) { if (message && Apps && Apps.isLoaded()) { const appMessage = Object.assign({}, originalMessage, message); - const prevent = Promise.await(Apps.getBridges().getListenerBridge().messageEvent('IPreMessageUpdatedPrevent', appMessage)); + const prevent = Promise.await(Apps.getBridges()?.getListenerBridge().messageEvent('IPreMessageUpdatedPrevent', appMessage)); if (prevent) { throw new Meteor.Error('error-app-prevented-updating', 'A Rocket.Chat App prevented the message updating.'); } let result; - result = Promise.await(Apps.getBridges().getListenerBridge().messageEvent('IPreMessageUpdatedExtend', appMessage)); - result = Promise.await(Apps.getBridges().getListenerBridge().messageEvent('IPreMessageUpdatedModify', result)); + result = Promise.await(Apps.getBridges()?.getListenerBridge().messageEvent('IPreMessageUpdatedExtend', appMessage)); + result = Promise.await(Apps.getBridges()?.getListenerBridge().messageEvent('IPreMessageUpdatedModify', result)); if (typeof result === 'object') { message = Object.assign(appMessage, result); @@ -38,8 +40,8 @@ export const updateMessage = function (message, user, originalMessage) { Messages.cloneAndSaveAsHistoryById(message._id, user); } - message.editedAt = new Date(); - message.editedBy = { + (message as IMessageEdited).editedAt = new Date(); + (message as IMessageEdited).editedBy = { _id: user._id, username: user.username, }; @@ -52,24 +54,23 @@ export const updateMessage = function (message, user, originalMessage) { if (message.msg && DISABLE_MESSAGE_PARSER !== 'true') { message.md = parser(message.msg); } - } catch (e) { - SystemLogger.error(e); // errors logged while the parser is at experimental stage + } catch (e: unknown) { + SystemLogger.error(String(e)); // errors logged while the parser is at experimental stage } - const tempid = message._id; - delete message._id; + const { _id, ...editedMessage } = message; - Messages.update({ _id: tempid }, { $set: message }); + Messages.update({ _id }, { $set: editedMessage }); const room = Rooms.findOneById(message.rid); - if (Apps && Apps.isLoaded()) { + if (Apps?.isLoaded()) { // This returns a promise, but it won't mutate anything about the message // so, we don't really care if it is successful or fails - Apps.getBridges().getListenerBridge().messageEvent('IPostMessageUpdated', message); + Apps.getBridges()?.getListenerBridge().messageEvent('IPostMessageUpdated', message); } Meteor.defer(function () { - callbacks.run('afterSaveMessage', Messages.findOneById(tempid), room, user._id); + callbacks.run('afterSaveMessage', Messages.findOneById(_id), room, user._id); }); }; diff --git a/app/lib/server/index.js b/app/lib/server/index.js index 044758894c5c..3f8c8d1b3ba5 100644 --- a/app/lib/server/index.js +++ b/app/lib/server/index.js @@ -7,7 +7,6 @@ import './startup/settingsOnLoadCdnPrefix'; import './startup/settingsOnLoadDirectReply'; import './startup/settingsOnLoadSMTP'; import '../lib/MessageTypes'; -import '../startup/defaultRoomTypes'; import './lib/bugsnag'; import './lib/debug'; import './lib/loginErrorMessageOverride'; @@ -51,7 +50,6 @@ import './methods/refreshOAuthService'; import './methods/removeOAuthService'; import './methods/removeUserFromPushSubscription'; import './methods/restartServer'; -import './methods/robotMethods'; import './methods/savePostProcessedMessage'; import './methods/savePushNotificationSubscription'; import './methods/saveSetting'; diff --git a/app/lib/server/lib/getRoomRoles.js b/app/lib/server/lib/getRoomRoles.js deleted file mode 100644 index 6ed6527c5368..000000000000 --- a/app/lib/server/lib/getRoomRoles.js +++ /dev/null @@ -1,32 +0,0 @@ -import _ from 'underscore'; - -import { settings } from '../../../settings'; -import { Subscriptions, Users } from '../../../models'; -import { Roles } from '../../../models/server/raw'; - -export function getRoomRoles(rid) { - const options = { - sort: { - 'u.username': 1, - }, - fields: { - rid: 1, - u: 1, - roles: 1, - }, - }; - - const UI_Use_Real_Name = settings.get('UI_Use_Real_Name') === true; - - const roles = Promise.await(Roles.find({ scope: 'Subscriptions', description: { $exists: 1, $ne: '' } }).toArray()); - const subscriptions = Subscriptions.findByRoomIdAndRoles(rid, _.pluck(roles, '_id'), options).fetch(); - - if (!UI_Use_Real_Name) { - return subscriptions; - } - return subscriptions.map((subscription) => { - const user = Users.findOneById(subscription.u._id); - subscription.u.name = user && user.name; - return subscription; - }); -} diff --git a/app/lib/server/lib/sendNotificationsOnMessage.js b/app/lib/server/lib/sendNotificationsOnMessage.js index 2d99c0375a01..950e0997eee1 100644 --- a/app/lib/server/lib/sendNotificationsOnMessage.js +++ b/app/lib/server/lib/sendNotificationsOnMessage.js @@ -5,7 +5,6 @@ import { hasPermission } from '../../../authorization'; import { settings } from '../../../settings/server'; import { callbacks } from '../../../../lib/callbacks'; import { Subscriptions, Users } from '../../../models/server'; -import { roomTypes } from '../../../utils'; import { callJoinRoom, messageContainsHighlight, @@ -17,6 +16,7 @@ import { sendWebPush, getPushData, shouldNotifyMobile, getNotificationPayload } import { notifyDesktopUser, shouldNotifyDesktop } from '../functions/notifications/desktop'; import { Notification } from '../../../notification-queue/server/NotificationQueue'; import { getMentions } from './notifyUsersOnMessage'; +import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; let TroubleshootDisableNotifications; @@ -237,7 +237,7 @@ export async function sendMessageNotifications(message, room, usersInThread = [] return; } - const sender = roomTypes.getConfig(room.t).getMsgSender(message.u._id); + const sender = roomCoordinator.getRoomDirectives(room.t)?.getMsgSender(message.u._id); if (!sender) { return message; } diff --git a/app/lib/server/methods/addOAuthService.js b/app/lib/server/methods/addOAuthService.ts similarity index 74% rename from app/lib/server/methods/addOAuthService.js rename to app/lib/server/methods/addOAuthService.ts index 36a4ba555ec2..4cf48d5ca332 100644 --- a/app/lib/server/methods/addOAuthService.js +++ b/app/lib/server/methods/addOAuthService.ts @@ -1,18 +1,20 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; -import { hasPermission } from '../../../authorization'; +import { hasPermission } from '../../../authorization/server'; import { addOAuthService } from '../functions/addOAuthService'; Meteor.methods({ addOAuthService(name) { check(name, String); - if (!Meteor.userId()) { + const userId = Meteor.userId(); + + if (!userId) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'addOAuthService' }); } - if (hasPermission(Meteor.userId(), 'add-oauth-service') !== true) { + if (hasPermission(userId, 'add-oauth-service') !== true) { throw new Meteor.Error('error-action-not-allowed', 'Adding OAuth Services is not allowed', { method: 'addOAuthService', action: 'Adding_OAuth_Services', diff --git a/app/lib/server/methods/addUserToRoom.js b/app/lib/server/methods/addUserToRoom.ts similarity index 100% rename from app/lib/server/methods/addUserToRoom.js rename to app/lib/server/methods/addUserToRoom.ts diff --git a/app/lib/server/methods/archiveRoom.js b/app/lib/server/methods/archiveRoom.ts similarity index 59% rename from app/lib/server/methods/archiveRoom.js rename to app/lib/server/methods/archiveRoom.ts index 4f101615b841..793312e4cc5c 100644 --- a/app/lib/server/methods/archiveRoom.js +++ b/app/lib/server/methods/archiveRoom.ts @@ -1,16 +1,19 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; -import { Rooms } from '../../../models'; -import { hasPermission } from '../../../authorization'; +import { Rooms } from '../../../models/server'; +import { hasPermission } from '../../../authorization/server'; import { archiveRoom } from '../functions'; -import { roomTypes, RoomMemberActions } from '../../../utils/server'; +import { RoomMemberActions } from '../../../../definition/IRoomTypeConfig'; +import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; Meteor.methods({ archiveRoom(rid) { check(rid, String); - if (!Meteor.userId()) { + const userId = Meteor.userId(); + + if (!userId) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'archiveRoom' }); } @@ -20,11 +23,11 @@ Meteor.methods({ throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'archiveRoom' }); } - if (!roomTypes.getConfig(room.t).allowMemberAction(room, RoomMemberActions.ARCHIVE)) { + if (!roomCoordinator.getRoomDirectives(room.t)?.allowMemberAction(room, RoomMemberActions.ARCHIVE)) { throw new Meteor.Error('error-direct-message-room', `rooms type: ${room.t} can not be archived`, { method: 'archiveRoom' }); } - if (!hasPermission(Meteor.userId(), 'archive-room', room._id)) { + if (!hasPermission(userId, 'archive-room', room._id)) { throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'archiveRoom' }); } diff --git a/app/lib/server/methods/blockUser.js b/app/lib/server/methods/blockUser.ts similarity index 72% rename from app/lib/server/methods/blockUser.js rename to app/lib/server/methods/blockUser.ts index f9e4bb4c54d2..124dfacae350 100644 --- a/app/lib/server/methods/blockUser.js +++ b/app/lib/server/methods/blockUser.ts @@ -1,9 +1,9 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; -import { Subscriptions } from '../../../models'; -import { roomTypes, RoomMemberActions } from '../../../utils/server'; -import { Rooms } from '../../../models/server'; +import { RoomMemberActions } from '../../../../definition/IRoomTypeConfig'; +import { Rooms, Subscriptions } from '../../../models/server'; +import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; Meteor.methods({ blockUser({ rid, blocked }) { @@ -16,7 +16,7 @@ Meteor.methods({ const room = Rooms.findOne({ _id: rid }); - if (!roomTypes.getConfig(room.t).allowMemberAction(room, RoomMemberActions.BLOCK)) { + if (!roomCoordinator.getRoomDirectives(room.t)?.allowMemberAction(room, RoomMemberActions.BLOCK)) { throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'blockUser' }); } diff --git a/app/lib/server/methods/checkRegistrationSecretURL.js b/app/lib/server/methods/checkRegistrationSecretURL.ts similarity index 81% rename from app/lib/server/methods/checkRegistrationSecretURL.js rename to app/lib/server/methods/checkRegistrationSecretURL.ts index ab411be95d1a..11a1d4ea4200 100644 --- a/app/lib/server/methods/checkRegistrationSecretURL.js +++ b/app/lib/server/methods/checkRegistrationSecretURL.ts @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; -import { settings } from '../../../settings'; +import { settings } from '../../../settings/server'; Meteor.methods({ checkRegistrationSecretURL(hash) { diff --git a/app/lib/server/methods/checkUsernameAvailability.js b/app/lib/server/methods/checkUsernameAvailability.ts similarity index 80% rename from app/lib/server/methods/checkUsernameAvailability.js rename to app/lib/server/methods/checkUsernameAvailability.ts index 7bf9b7f9acdb..2a9f34780f2a 100644 --- a/app/lib/server/methods/checkUsernameAvailability.js +++ b/app/lib/server/methods/checkUsernameAvailability.ts @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; -import { settings } from '../../../settings'; +import { settings } from '../../../settings/server'; import { checkUsernameAvailability } from '../functions'; import { RateLimiter } from '../lib'; @@ -15,11 +15,11 @@ Meteor.methods({ const user = Meteor.user(); - if (user.username && !settings.get('Accounts_AllowUsernameChange')) { + if (user?.username && !settings.get('Accounts_AllowUsernameChange')) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'setUsername' }); } - if (user.username === username) { + if (user?.username === username) { return true; } return checkUsernameAvailability(username); diff --git a/app/lib/server/methods/createChannel.js b/app/lib/server/methods/createChannel.ts similarity index 67% rename from app/lib/server/methods/createChannel.js rename to app/lib/server/methods/createChannel.ts index 0201e985e74f..a7654d5fea5b 100644 --- a/app/lib/server/methods/createChannel.js +++ b/app/lib/server/methods/createChannel.ts @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; -import { hasPermission } from '../../../authorization'; +import { hasPermission } from '../../../authorization/server'; import { createRoom } from '../functions'; Meteor.methods({ @@ -9,14 +9,18 @@ Meteor.methods({ check(name, String); check(members, Match.Optional([String])); - if (!Meteor.userId()) { + const uid = Meteor.userId(); + + const user = Meteor.user(); + + if (!uid || !user?.username) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'createChannel' }); } - if (!hasPermission(Meteor.userId(), 'create-c')) { + if (!hasPermission(uid, 'create-c')) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'createChannel' }); } - return createRoom('c', name, Meteor.user() && Meteor.user().username, members, readOnly, { + return createRoom('c', name, user.username, members, readOnly, { customFields, ...extraData, }); diff --git a/app/lib/server/methods/createToken.js b/app/lib/server/methods/createToken.ts similarity index 61% rename from app/lib/server/methods/createToken.js rename to app/lib/server/methods/createToken.ts index 47713c0fc44e..91de147915da 100644 --- a/app/lib/server/methods/createToken.js +++ b/app/lib/server/methods/createToken.ts @@ -1,13 +1,16 @@ import { Meteor } from 'meteor/meteor'; import { Accounts } from 'meteor/accounts-base'; -import { hasPermission } from '../../../authorization'; +import { hasPermission } from '../../../authorization/server'; Meteor.methods({ createToken(userId) { + const uid = Meteor.userId(); + if ( - !['yes', 'true'].includes(process.env.CREATE_TOKENS_FOR_USERS) || - (Meteor.userId() !== userId && !hasPermission(Meteor.userId(), 'user-generate-access-token')) + !['yes', 'true'].includes(String(process.env.CREATE_TOKENS_FOR_USERS)) || + !uid || + (uid !== userId && !hasPermission(uid, 'user-generate-access-token')) ) { throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'createToken' }); } diff --git a/app/lib/server/methods/deleteMessage.js b/app/lib/server/methods/deleteMessage.ts similarity index 85% rename from app/lib/server/methods/deleteMessage.js rename to app/lib/server/methods/deleteMessage.ts index b5dbec462fc1..2987c5d8b0db 100644 --- a/app/lib/server/methods/deleteMessage.js +++ b/app/lib/server/methods/deleteMessage.ts @@ -2,8 +2,9 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; import { canDeleteMessage } from '../../../authorization/server/functions/canDeleteMessage'; -import { Messages } from '../../../models'; +import { Messages } from '../../../models/server'; import { deleteMessage } from '../functions'; +import { IUser } from '../../../../definition/IUser'; Meteor.methods({ async deleteMessage(message) { @@ -44,6 +45,6 @@ Meteor.methods({ }); } - return deleteMessage(originalMessage, Meteor.user()); + return deleteMessage(originalMessage, Meteor.user() as IUser); }, }); diff --git a/app/lib/server/methods/deleteUserOwnAccount.js b/app/lib/server/methods/deleteUserOwnAccount.ts similarity index 84% rename from app/lib/server/methods/deleteUserOwnAccount.js rename to app/lib/server/methods/deleteUserOwnAccount.ts index f6dda0888feb..cfdf804dbc39 100644 --- a/app/lib/server/methods/deleteUserOwnAccount.js +++ b/app/lib/server/methods/deleteUserOwnAccount.ts @@ -4,8 +4,8 @@ import { Accounts } from 'meteor/accounts-base'; import { SHA256 } from 'meteor/sha'; import s from 'underscore.string'; -import { settings } from '../../../settings'; -import { Users } from '../../../models'; +import { settings } from '../../../settings/server'; +import { Users } from '../../../models/server'; import { deleteUser } from '../functions'; Meteor.methods({ @@ -24,10 +24,10 @@ Meteor.methods({ }); } - const userId = Meteor.userId(); - const user = Users.findOneById(userId); + const uid = Meteor.userId(); + const user = Users.findOneById(uid); - if (!user) { + if (!user || !uid) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'deleteUserOwnAccount', }); @@ -49,7 +49,7 @@ Meteor.methods({ }); } - await deleteUser(userId, confirmRelinquish); + await deleteUser(uid, confirmRelinquish); return true; }, diff --git a/app/lib/server/methods/executeSlashCommandPreview.js b/app/lib/server/methods/executeSlashCommandPreview.ts similarity index 94% rename from app/lib/server/methods/executeSlashCommandPreview.js rename to app/lib/server/methods/executeSlashCommandPreview.ts index 52f2c18527a5..8df013402607 100644 --- a/app/lib/server/methods/executeSlashCommandPreview.js +++ b/app/lib/server/methods/executeSlashCommandPreview.ts @@ -1,6 +1,6 @@ import { Meteor } from 'meteor/meteor'; -import { slashCommands } from '../../../utils'; +import { slashCommands } from '../../../utils/server'; Meteor.methods({ executeSlashCommandPreview(command, preview) { diff --git a/app/lib/server/methods/getChannelHistory.js b/app/lib/server/methods/getChannelHistory.ts similarity index 97% rename from app/lib/server/methods/getChannelHistory.js rename to app/lib/server/methods/getChannelHistory.ts index d39499a84524..4557282191a1 100644 --- a/app/lib/server/methods/getChannelHistory.js +++ b/app/lib/server/methods/getChannelHistory.ts @@ -17,6 +17,10 @@ Meteor.methods({ } const fromUserId = Meteor.userId(); + if (!fromUserId) { + return false; + } + const room = Rooms.findOneById(rid); if (!room) { return false; @@ -47,7 +51,7 @@ Meteor.methods({ const hiddenMessageTypes = getHiddenSystemMessages(room); - const options = { + const options: Record = { sort: { ts: -1, }, diff --git a/app/lib/server/methods/getMessages.js b/app/lib/server/methods/getMessages.js deleted file mode 100644 index f8ccbbbb6f74..000000000000 --- a/app/lib/server/methods/getMessages.js +++ /dev/null @@ -1,22 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { check } from 'meteor/check'; - -import { canAccessRoom } from '../../../authorization/server'; -import { Messages } from '../../../models/server'; - -Meteor.methods({ - getMessages(messages) { - check(messages, [String]); - - const msgs = Messages.findVisibleByIds(messages).fetch(); - - const user = { _id: Meteor.userId() }; - - const rids = [...new Set(msgs.map((m) => m.rid))]; - if (!rids.every((_id) => canAccessRoom({ _id }, user))) { - throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'getSingleMessage' }); - } - - return msgs; - }, -}); diff --git a/app/lib/server/methods/getMessages.ts b/app/lib/server/methods/getMessages.ts new file mode 100644 index 000000000000..7f4dbea9d452 --- /dev/null +++ b/app/lib/server/methods/getMessages.ts @@ -0,0 +1,26 @@ +import { Meteor } from 'meteor/meteor'; +import { check } from 'meteor/check'; + +import { canAccessRoomId } from '../../../authorization/server'; +import { Messages } from '../../../models/server'; +import { IMessage } from '../../../../definition/IMessage'; + +Meteor.methods({ + getMessages(messages) { + check(messages, [String]); + const uid = Meteor.userId(); + + if (!uid) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getMessages' }); + } + + const msgs = Messages.findVisibleByIds(messages).fetch() as IMessage[]; + const rids = [...new Set(msgs.map((m) => m.rid))]; + + if (!rids.every((_id) => canAccessRoomId(_id, uid))) { + throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'getSingleMessage' }); + } + + return msgs; + }, +}); diff --git a/app/lib/server/methods/getRoomJoinCode.js b/app/lib/server/methods/getRoomJoinCode.ts similarity index 62% rename from app/lib/server/methods/getRoomJoinCode.js rename to app/lib/server/methods/getRoomJoinCode.ts index da58b6b86e83..e599684bd046 100644 --- a/app/lib/server/methods/getRoomJoinCode.js +++ b/app/lib/server/methods/getRoomJoinCode.ts @@ -1,23 +1,25 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; -import { hasPermission } from '../../../authorization'; -import { Rooms } from '../../../models'; +import { hasPermission } from '../../../authorization/server'; +import { Rooms } from '../../../models/server'; Meteor.methods({ getRoomJoinCode(rid) { check(rid, String); - if (!Meteor.userId()) { + const userId = Meteor.userId(); + + if (!userId) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getJoinCode' }); } - if (!hasPermission(Meteor.userId(), 'view-join-code')) { + if (!hasPermission(userId, 'view-join-code')) { throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'getJoinCode' }); } const [room] = Rooms.findById(rid).fetch(); - return room && room.joinCode; + return room?.joinCode; }, }); diff --git a/app/lib/server/methods/getRoomRoles.js b/app/lib/server/methods/getRoomRoles.ts similarity index 75% rename from app/lib/server/methods/getRoomRoles.js rename to app/lib/server/methods/getRoomRoles.ts index 33b6ef34edad..879c53db5d0e 100644 --- a/app/lib/server/methods/getRoomRoles.js +++ b/app/lib/server/methods/getRoomRoles.ts @@ -1,8 +1,8 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; -import { settings } from '../../../settings'; -import { getRoomRoles } from '../lib/getRoomRoles'; +import { settings } from '../../../settings/server'; +import { getRoomRoles } from '../../../../server/lib/roles/getRoomRoles'; Meteor.methods({ getRoomRoles(rid) { diff --git a/app/lib/server/methods/getSingleMessage.js b/app/lib/server/methods/getSingleMessage.ts similarity index 62% rename from app/lib/server/methods/getSingleMessage.js rename to app/lib/server/methods/getSingleMessage.ts index 604ac2f1b4f7..81676f03f28f 100644 --- a/app/lib/server/methods/getSingleMessage.js +++ b/app/lib/server/methods/getSingleMessage.ts @@ -1,20 +1,26 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; -import { canAccessRoom } from '../../../authorization/server'; +import { canAccessRoomId } from '../../../authorization/server'; import { Messages } from '../../../models/server'; Meteor.methods({ getSingleMessage(msgId) { check(msgId, String); + const uid = Meteor.userId(); + + if (!uid) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getSingleMessage' }); + } + const msg = Messages.findOneById(msgId); if (!msg || !msg.rid) { return undefined; } - if (!canAccessRoom({ _id: msg.rid }, { _id: Meteor.userId() })) { + if (!canAccessRoomId(msg.rid, uid)) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'getSingleMessage' }); } diff --git a/app/lib/server/methods/getSlashCommandPreviews.js b/app/lib/server/methods/getSlashCommandPreviews.ts similarity index 93% rename from app/lib/server/methods/getSlashCommandPreviews.js rename to app/lib/server/methods/getSlashCommandPreviews.ts index a64359321735..2d8b7ce179b5 100644 --- a/app/lib/server/methods/getSlashCommandPreviews.js +++ b/app/lib/server/methods/getSlashCommandPreviews.ts @@ -1,6 +1,6 @@ import { Meteor } from 'meteor/meteor'; -import { slashCommands } from '../../../utils'; +import { slashCommands } from '../../../utils/server'; Meteor.methods({ getSlashCommandPreviews(command) { diff --git a/app/lib/server/methods/joinDefaultChannels.js b/app/lib/server/methods/joinDefaultChannels.ts similarity index 68% rename from app/lib/server/methods/joinDefaultChannels.js rename to app/lib/server/methods/joinDefaultChannels.ts index 3732d5fe8e10..9b43ae8b8054 100644 --- a/app/lib/server/methods/joinDefaultChannels.js +++ b/app/lib/server/methods/joinDefaultChannels.ts @@ -2,16 +2,18 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; import { addUserToDefaultChannels } from '../functions'; +import { IUser } from '../../../../definition/IUser'; Meteor.methods({ joinDefaultChannels(silenced) { check(silenced, Match.Optional(Boolean)); + const user = Meteor.user(); - if (!Meteor.userId()) { + if (!user) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'joinDefaultChannels', }); } - return addUserToDefaultChannels(Meteor.user(), silenced); + return addUserToDefaultChannels(user as IUser, Boolean(silenced)); }, }); diff --git a/app/lib/server/methods/joinRoom.js b/app/lib/server/methods/joinRoom.ts similarity index 76% rename from app/lib/server/methods/joinRoom.js rename to app/lib/server/methods/joinRoom.ts index 9460f68cf672..76fd5c918e87 100644 --- a/app/lib/server/methods/joinRoom.js +++ b/app/lib/server/methods/joinRoom.ts @@ -1,17 +1,20 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; -import { hasPermission, canAccessRoom } from '../../../authorization'; -import { Rooms } from '../../../models'; +import { hasPermission, canAccessRoom } from '../../../authorization/server'; +import { Rooms } from '../../../models/server'; import { Tokenpass, updateUserTokenpassBalances } from '../../../tokenpass/server'; import { addUserToRoom } from '../functions'; -import { roomTypes, RoomMemberActions } from '../../../utils/server'; +import { RoomMemberActions } from '../../../../definition/IRoomTypeConfig'; +import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; Meteor.methods({ joinRoom(rid, code) { check(rid, String); - if (!Meteor.userId()) { + const user = Meteor.user(); + + if (!user) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'joinRoom' }); } @@ -21,12 +24,12 @@ Meteor.methods({ throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'joinRoom' }); } - if (!roomTypes.getConfig(room.t).allowMemberAction(room, RoomMemberActions.JOIN)) { + if (!roomCoordinator.getRoomDirectives(room.t)?.allowMemberAction(room, RoomMemberActions.JOIN)) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'joinRoom' }); } // TODO we should have a 'beforeJoinRoom' call back so external services can do their own validations - const user = Meteor.user(); + if (room.tokenpass && user && user.services && user.services.tokenpass) { const balances = updateUserTokenpassBalances(user); @@ -34,10 +37,10 @@ Meteor.methods({ throw new Meteor.Error('error-not-allowed', 'Token required', { method: 'joinRoom' }); } } else { - if (!canAccessRoom(room, Meteor.user())) { + if (!canAccessRoom(room, user)) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'joinRoom' }); } - if (room.joinCodeRequired === true && code !== room.joinCode && !hasPermission(Meteor.userId(), 'join-without-join-code')) { + if (room.joinCodeRequired === true && code !== room.joinCode && !hasPermission(user._id, 'join-without-join-code')) { throw new Meteor.Error('error-code-invalid', 'Invalid Room Password', { method: 'joinRoom', }); diff --git a/app/lib/server/methods/leaveRoom.ts b/app/lib/server/methods/leaveRoom.ts index ca47466a1d3d..79301b119aa8 100644 --- a/app/lib/server/methods/leaveRoom.ts +++ b/app/lib/server/methods/leaveRoom.ts @@ -4,8 +4,10 @@ import { check } from 'meteor/check'; import { hasPermission, hasRole } from '../../../authorization/server'; import { Subscriptions, Rooms } from '../../../models/server'; import { removeUserFromRoom } from '../functions'; -import { roomTypes, RoomMemberActions } from '../../../utils/server'; +import { RoomMemberActions } from '../../../../definition/IRoomTypeConfig'; import { Roles } from '../../../models/server/raw'; +import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; +import { IUser } from '../../../../definition/IUser'; Meteor.methods({ async leaveRoom(rid) { @@ -16,9 +18,9 @@ Meteor.methods({ } const room = Rooms.findOneById(rid); - const user = Meteor.user(); + const user = Meteor.user() as unknown as IUser; - if (!user || !roomTypes.getConfig(room.t).allowMemberAction(room, RoomMemberActions.LEAVE)) { + if (!user || !roomCoordinator.getRoomDirectives(room.t)?.allowMemberAction(room, RoomMemberActions.LEAVE)) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'leaveRoom' }); } diff --git a/app/lib/server/methods/refreshOAuthService.ts b/app/lib/server/methods/refreshOAuthService.ts index d4811333573c..1373cd10d436 100644 --- a/app/lib/server/methods/refreshOAuthService.ts +++ b/app/lib/server/methods/refreshOAuthService.ts @@ -6,13 +6,15 @@ import { Settings } from '../../../models/server/raw'; Meteor.methods({ async refreshOAuthService() { - if (!Meteor.userId()) { + const userId = Meteor.userId(); + + if (!userId) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'refreshOAuthService', }); } - if (hasPermission(Meteor.userId(), 'add-oauth-service') !== true) { + if (hasPermission(userId, 'add-oauth-service') !== true) { throw new Meteor.Error('error-action-not-allowed', 'Refresh OAuth Services is not allowed', { method: 'refreshOAuthService', action: 'Refreshing_OAuth_Services', diff --git a/app/lib/server/methods/removeOAuthService.ts b/app/lib/server/methods/removeOAuthService.ts index bfddcf64eb9d..419992e2b0e5 100644 --- a/app/lib/server/methods/removeOAuthService.ts +++ b/app/lib/server/methods/removeOAuthService.ts @@ -9,13 +9,15 @@ Meteor.methods({ async removeOAuthService(name) { check(name, String); - if (!Meteor.userId()) { + const userId = Meteor.userId(); + + if (!userId) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'removeOAuthService', }); } - if (hasPermission(Meteor.userId(), 'add-oauth-service') !== true) { + if (hasPermission(userId, 'add-oauth-service') !== true) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'removeOAuthService' }); } diff --git a/app/lib/server/methods/restartServer.js b/app/lib/server/methods/restartServer.ts similarity index 71% rename from app/lib/server/methods/restartServer.js rename to app/lib/server/methods/restartServer.ts index d694b833f746..f321461d6359 100644 --- a/app/lib/server/methods/restartServer.js +++ b/app/lib/server/methods/restartServer.ts @@ -1,14 +1,17 @@ import { Meteor } from 'meteor/meteor'; -import { hasRole } from '../../../authorization'; +import { hasPermission } from '../../../authorization/server'; Meteor.methods({ + // eslint-disable-next-line @typescript-eslint/camelcase restart_server() { - if (!Meteor.userId()) { + const uid = Meteor.userId(); + + if (!uid) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'restart_server' }); } - if (hasRole(Meteor.userId(), 'admin') !== true) { + if (hasPermission(uid, 'restart-server') !== true) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'restart_server' }); } diff --git a/app/lib/server/methods/robotMethods.js b/app/lib/server/methods/robotMethods.js deleted file mode 100644 index d93d3fa57bf3..000000000000 --- a/app/lib/server/methods/robotMethods.js +++ /dev/null @@ -1,32 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { check } from 'meteor/check'; -import _ from 'underscore'; - -import { hasRole } from '../../../authorization'; -import * as Models from '../../../models'; - -Meteor.methods({ - 'robot.modelCall'(model, method, args) { - check(model, String); - check(method, String); - if (!Meteor.userId()) { - throw new Meteor.Error('error-invalid-user', 'Invalid user', { - method: 'robot.modelCall', - }); - } - if (!hasRole(Meteor.userId(), 'robot')) { - throw new Meteor.Error('error-not-allowed', 'Not allowed', { - method: 'robot.modelCall', - }); - } - const m = Models[model]; - - if (!m || !_.isFunction(m[method])) { - throw new Meteor.Error('error-invalid-method', 'Invalid method', { - method: 'robot.modelCall', - }); - } - const cursor = Models[model][method].apply(Models[model], args); - return cursor && cursor.fetch ? cursor.fetch() : cursor; - }, -}); diff --git a/app/lib/server/methods/saveCustomFields.ts b/app/lib/server/methods/saveCustomFields.ts index d30055f1977e..35baf78751cf 100644 --- a/app/lib/server/methods/saveCustomFields.ts +++ b/app/lib/server/methods/saveCustomFields.ts @@ -5,7 +5,11 @@ import { saveCustomFields } from '../functions/saveCustomFields'; Meteor.methods({ saveCustomFields(fields = {}) { - saveCustomFields(Meteor.userId(), fields); + const uid = Meteor.userId(); + if (!uid) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'saveCustomFields' }); + } + saveCustomFields(uid, fields); }, }); diff --git a/app/lib/server/methods/setAdminStatus.js b/app/lib/server/methods/setAdminStatus.ts similarity index 76% rename from app/lib/server/methods/setAdminStatus.js rename to app/lib/server/methods/setAdminStatus.ts index 9ea6d5259d31..2f416fe8e6d7 100644 --- a/app/lib/server/methods/setAdminStatus.js +++ b/app/lib/server/methods/setAdminStatus.ts @@ -1,26 +1,28 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; -import { hasPermission } from '../../../authorization'; +import { hasPermission } from '../../../authorization/server'; Meteor.methods({ setAdminStatus(userId, admin) { check(userId, String); check(admin, Match.Optional(Boolean)); - if (!Meteor.userId()) { + const uid = Meteor.userId(); + + if (!uid) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'setAdminStatus' }); } - if (hasPermission(Meteor.userId(), 'assign-admin-role') !== true) { + if (hasPermission(uid, 'assign-admin-role') !== true) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'setAdminStatus' }); } const user = Meteor.users.findOne({ _id: userId }, { fields: { username: 1 } }); if (admin) { - return Meteor.call('authorization:addUserToRole', 'admin', user.username); + return Meteor.call('authorization:addUserToRole', 'admin', user?.username); } - return Meteor.call('authorization:removeUserFromRole', 'admin', user.username); + return Meteor.call('authorization:removeUserFromRole', 'admin', user?.username); }, }); diff --git a/app/lib/server/methods/setEmail.js b/app/lib/server/methods/setEmail.ts similarity index 92% rename from app/lib/server/methods/setEmail.js rename to app/lib/server/methods/setEmail.ts index 027e6418145c..682f936defc7 100644 --- a/app/lib/server/methods/setEmail.js +++ b/app/lib/server/methods/setEmail.ts @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; -import { settings } from '../../../settings'; +import { settings } from '../../../settings/server'; import { setEmail } from '../functions'; import { RateLimiter } from '../lib'; @@ -9,12 +9,12 @@ Meteor.methods({ setEmail(email) { check(email, String); - if (!Meteor.userId()) { + const user = Meteor.user(); + + if (!user) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'setEmail' }); } - const user = Meteor.user(); - if (!settings.get('Accounts_AllowEmailChange')) { throw new Meteor.Error('error-action-not-allowed', 'Changing email is not allowed', { method: 'setEmail', diff --git a/app/lib/server/methods/setRealName.js b/app/lib/server/methods/setRealName.ts similarity index 93% rename from app/lib/server/methods/setRealName.js rename to app/lib/server/methods/setRealName.ts index 5f8301667a34..0ebc9ebb76bf 100644 --- a/app/lib/server/methods/setRealName.js +++ b/app/lib/server/methods/setRealName.ts @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; -import { settings } from '../../../settings'; +import { settings } from '../../../settings/server'; import { setRealName } from '../functions'; import { RateLimiter } from '../lib'; diff --git a/app/lib/server/methods/setUsername.js b/app/lib/server/methods/setUsername.ts similarity index 94% rename from app/lib/server/methods/setUsername.js rename to app/lib/server/methods/setUsername.ts index 91e1d60d6d86..42a141a29fd9 100644 --- a/app/lib/server/methods/setUsername.js +++ b/app/lib/server/methods/setUsername.ts @@ -2,8 +2,8 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; import _ from 'underscore'; -import { settings } from '../../../settings'; -import { Users } from '../../../models'; +import { settings } from '../../../settings/server'; +import { Users } from '../../../models/server'; import { callbacks } from '../../../../lib/callbacks'; import { checkUsernameAvailability } from '../functions'; import { RateLimiter } from '../lib'; @@ -14,12 +14,12 @@ Meteor.methods({ const { joinDefaultChannelsSilenced } = param; check(username, String); - if (!Meteor.userId()) { + const user = Meteor.user(); + + if (!user) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'setUsername' }); } - const user = Meteor.user(); - if (user.username && !settings.get('Accounts_AllowUsernameChange')) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'setUsername' }); } diff --git a/app/lib/server/methods/unarchiveRoom.js b/app/lib/server/methods/unarchiveRoom.ts similarity index 72% rename from app/lib/server/methods/unarchiveRoom.js rename to app/lib/server/methods/unarchiveRoom.ts index e584ce1044d3..83f38f4e19e8 100644 --- a/app/lib/server/methods/unarchiveRoom.js +++ b/app/lib/server/methods/unarchiveRoom.ts @@ -1,15 +1,17 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; -import { hasPermission } from '../../../authorization'; -import { Rooms } from '../../../models'; +import { hasPermission } from '../../../authorization/server'; +import { Rooms } from '../../../models/server'; import { unarchiveRoom } from '../functions'; Meteor.methods({ unarchiveRoom(rid) { check(rid, String); - if (!Meteor.userId()) { + const userId = Meteor.userId(); + + if (!userId) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'unarchiveRoom' }); } @@ -19,7 +21,7 @@ Meteor.methods({ throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'unarchiveRoom' }); } - if (!hasPermission(Meteor.userId(), 'unarchive-room', room._id)) { + if (!hasPermission(userId, 'unarchive-room', room._id)) { throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'unarchiveRoom' }); } diff --git a/app/lib/server/methods/unblockUser.js b/app/lib/server/methods/unblockUser.ts similarity index 92% rename from app/lib/server/methods/unblockUser.js rename to app/lib/server/methods/unblockUser.ts index 3b3522feebd8..4a4650e6fd40 100644 --- a/app/lib/server/methods/unblockUser.js +++ b/app/lib/server/methods/unblockUser.ts @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; -import { Subscriptions } from '../../../models'; +import { Subscriptions } from '../../../models/server'; Meteor.methods({ unblockUser({ rid, blocked }) { diff --git a/app/lib/server/startup/settings.ts b/app/lib/server/startup/settings.ts index d97e4dc050be..90af8d3dccae 100644 --- a/app/lib/server/startup/settings.ts +++ b/app/lib/server/startup/settings.ts @@ -1351,6 +1351,14 @@ settingsRegistry.addGroup('Message', function () { public: true, }, ); + this.add('Message_Auditing_Panel_Load_Count', 0, { + type: 'int', + hidden: true, + }); + this.add('Message_Auditing_Apply_Count', 0, { + type: 'int', + hidden: true, + }); }); settingsRegistry.addGroup('Meta', function () { @@ -3094,6 +3102,13 @@ settingsRegistry.addGroup('Setup_Wizard', function () { secret: true, }); + this.add('Cloud_Workspace_Had_Trial', false, { + type: 'boolean', + hidden: true, + readonly: true, + secret: true, + }); + this.add('Cloud_Workspace_Access_Token', '', { type: 'string', hidden: true, @@ -3240,3 +3255,106 @@ settingsRegistry.addGroup('Troubleshoot', function () { alert: 'Troubleshoot_Disable_Workspace_Sync_Alert', }); }); + +settingsRegistry.addGroup('Call_Center', function () { + this.with({ tab: 'Settings' }, function () { + this.add('VoIP_Enabled', false, { + type: 'boolean', + public: true, + alert: 'Experimental_Feature_Alert', + enableQuery: { + _id: 'Livechat_enabled', + value: true, + }, + }); + this.add('VoIP_JWT_Secret', '', { + type: 'password', + i18nDescription: 'VoIP_JWT_Secret_description', + enableQuery: { + _id: 'VoIP_Enabled', + value: true, + }, + }); + this.section('Server_Configuration', function () { + this.add('VoIP_Server_Host', '', { + type: 'string', + public: true, + enableQuery: { + _id: 'VoIP_Enabled', + value: true, + }, + }); + this.add('VoIP_Server_Websocket_Port', 0, { + type: 'int', + public: true, + enableQuery: { + _id: 'VoIP_Enabled', + value: true, + }, + }); + this.add('VoIP_Server_Name', '', { + type: 'string', + public: true, + enableQuery: { + _id: 'VoIP_Enabled', + value: true, + }, + }); + this.add('VoIP_Server_Websocket_Path', '', { + type: 'string', + public: true, + enableQuery: { + _id: 'VoIP_Enabled', + value: true, + }, + }); + }); + + this.section('Management_Server', function () { + this.add('VoIP_Management_Server_Host', '', { + type: 'string', + public: true, + enableQuery: { + _id: 'VoIP_Enabled', + value: true, + }, + }); + + this.add('VoIP_Management_Server_Port', 0, { + type: 'int', + public: true, + enableQuery: { + _id: 'VoIP_Enabled', + value: true, + }, + }); + + this.add('VoIP_Management_Server_Name', '', { + type: 'string', + public: true, + enableQuery: { + _id: 'VoIP_Enabled', + value: true, + }, + }); + + this.add('VoIP_Management_Server_Username', '', { + type: 'string', + public: true, + enableQuery: { + _id: 'VoIP_Enabled', + value: true, + }, + }); + + this.add('VoIP_Management_Server_Password', '', { + type: 'password', + public: true, + enableQuery: { + _id: 'VoIP_Enabled', + value: true, + }, + }); + }); + }); +}); diff --git a/app/lib/startup/defaultRoomTypes.js b/app/lib/startup/defaultRoomTypes.js deleted file mode 100644 index a5b844aebd34..000000000000 --- a/app/lib/startup/defaultRoomTypes.js +++ /dev/null @@ -1,16 +0,0 @@ -import { roomTypes } from '../../utils'; -import { - ConversationRoomType, - DirectMessageRoomType, - FavoriteRoomType, - PrivateRoomType, - PublicRoomType, - UnreadRoomType, -} from '../lib/roomTypes'; - -roomTypes.add(new UnreadRoomType()); -roomTypes.add(new FavoriteRoomType()); -roomTypes.add(new ConversationRoomType()); -roomTypes.add(new PublicRoomType()); -roomTypes.add(new PrivateRoomType()); -roomTypes.add(new DirectMessageRoomType()); diff --git a/app/livechat/client/index.js b/app/livechat/client/index.js index 2432ca73a6f8..645d89170532 100644 --- a/app/livechat/client/index.js +++ b/app/livechat/client/index.js @@ -1,6 +1,6 @@ import '../lib/messageTypes'; -import './roomType'; import './route'; +import './voip'; import './ui'; import './tabBar'; import './startup/notifyUnreadRooms'; diff --git a/app/livechat/client/lib/stream/queueManager.js b/app/livechat/client/lib/stream/queueManager.js index a63979ff19ad..f76e5679d279 100644 --- a/app/livechat/client/lib/stream/queueManager.js +++ b/app/livechat/client/lib/stream/queueManager.js @@ -45,7 +45,7 @@ const updateCollection = (inquiry) => { }; const getInquiriesFromAPI = async () => { - const { inquiries } = await APIClient.v1.get('livechat/inquiries.queued?sort={"ts": 1}'); + const { inquiries } = await APIClient.v1.get('livechat/inquiries.queuedForUser?sort={"ts": 1}'); return inquiries; }; @@ -59,7 +59,7 @@ const appendListenerToDepartment = (departmentId) => { inquiryDataStream.on(`department/${departmentId}`, updateCollection); return () => removeListenerOfDepartment(departmentId); }; -const addListenerForeachDepartment = async (departments = []) => { +const addListenerForeachDepartment = (departments = []) => { const cleanupFunctions = departments.map((department) => appendListenerToDepartment(department)); return () => cleanupFunctions.forEach((cleanup) => cleanup()); }; @@ -87,14 +87,17 @@ const subscribe = async (userId) => { const agentDepartments = (await getAgentsDepartments(userId)).map((department) => department.departmentId); - const cleanUp = agentDepartments.length ? await addListenerForeachDepartment(agentDepartments) : addGlobalListener(); + // Register to all depts + public queue always to match the inquiry list returned by backend + const cleanDepartmentListeners = addListenerForeachDepartment(agentDepartments); + const globalCleanup = addGlobalListener(); updateInquiries(await getInquiriesFromAPI()); return () => { LivechatInquiry.remove({}); removeGlobalListener(); - cleanUp && cleanUp(); + cleanDepartmentListeners && cleanDepartmentListeners(); + globalCleanup && globalCleanup(); departments.clear(); }; }; diff --git a/app/livechat/client/roomType.js b/app/livechat/client/roomType.js deleted file mode 100644 index 4209e4f617c9..000000000000 --- a/app/livechat/client/roomType.js +++ /dev/null @@ -1,4 +0,0 @@ -import { roomTypes } from '../../utils'; -import LivechatRoomType from '../lib/LivechatRoomType'; - -roomTypes.add(new LivechatRoomType()); diff --git a/app/livechat/client/tabBar.ts b/app/livechat/client/tabBar.ts index 1ef8df95426c..e7698b8f3239 100644 --- a/app/livechat/client/tabBar.ts +++ b/app/livechat/client/tabBar.ts @@ -3,7 +3,7 @@ import { lazy } from 'react'; import { addAction } from '../../../client/views/room/lib/Toolbox'; addAction('room-info', { - groups: ['live'], + groups: ['live' /* , 'voip'*/], id: 'room-info', title: 'Room_Info', icon: 'info-circled', @@ -11,8 +11,17 @@ addAction('room-info', { order: 0, }); +addAction('voip-room-info', { + groups: ['voip'], + id: 'voip-room-info', + title: 'Call_Information', + icon: 'info-circled', + template: lazy(() => import('../../../client/views/omnichannel/directory/calls/contextualBar/CallsContextualBarRoom')), + order: 0, +}); + addAction('contact-chat-history', { - groups: ['live'], + groups: ['live' /* , 'voip'*/], id: 'contact-chat-history', title: 'Contact_Chat_History', icon: 'clock', diff --git a/app/livechat/client/views/app/dialog/closeRoom.js b/app/livechat/client/views/app/dialog/closeRoom.js index 6e7244946e79..3db0d930cfc9 100644 --- a/app/livechat/client/views/app/dialog/closeRoom.js +++ b/app/livechat/client/views/app/dialog/closeRoom.js @@ -6,7 +6,7 @@ import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { settings } from '../../../../../settings'; import { modal } from '../../../../../ui-utils/client'; import { APIClient, t } from '../../../../../utils'; -import { hasRole } from '../../../../../authorization'; +import { hasAnyRole } from '../../../../../authorization'; import './closeRoom.html'; import { handleError } from '../../../../../../client/lib/utils/handleError'; @@ -27,7 +27,7 @@ const validateRoomTags = (tagsRequired, tags) => { }; const checkUserTagPermission = (availableUserTags = [], tag) => { - if (hasRole(Meteor.userId(), ['admin', 'livechat-manager'])) { + if (hasAnyRole(Meteor.userId(), ['admin', 'livechat-manager'])) { return true; } @@ -182,7 +182,7 @@ Template.closeRoom.onCreated(async function () { Meteor.call('livechat:getTagsList', (err, tagsList) => { this.availableTags.set(tagsList); - const isAdmin = hasRole(uid, ['admin', 'livechat-manager']); + const isAdmin = hasAnyRole(uid, ['admin', 'livechat-manager']); const availableTags = tagsList .filter(({ departments }) => isAdmin || departments.length === 0 || departments.some((i) => agentDepartments.includes(i))) .map(({ name }) => name); diff --git a/app/livechat/client/views/app/livechatReadOnly.html b/app/livechat/client/views/app/livechatReadOnly.html index c2c878a8d29f..0ff465acd25d 100644 --- a/app/livechat/client/views/app/livechatReadOnly.html +++ b/app/livechat/client/views/app/livechatReadOnly.html @@ -1,8 +1,8 @@ diff --git a/app/livechat/client/views/app/livechatReadOnly.js b/app/livechat/client/views/app/livechatReadOnly.js index ac95a2c347d2..fa38742ca152 100644 --- a/app/livechat/client/views/app/livechatReadOnly.js +++ b/app/livechat/client/views/app/livechatReadOnly.js @@ -7,13 +7,14 @@ import { ChatRoom, CachedChatRoom } from '../../../../models'; import { callWithErrorHandling } from '../../../../../client/lib/utils/callWithErrorHandling'; import './livechatReadOnly.html'; import { APIClient } from '../../../../utils/client'; +import { RoomManager } from '../../../../ui-utils/client/lib/RoomManager'; import { inquiryDataStream } from '../../lib/stream/inquiry'; +import { handleError } from '../../../../../client/lib/utils/handleError'; Template.livechatReadOnly.helpers({ inquiryOpen() { const inquiry = Template.instance().inquiry.get(); - const room = Template.instance().room.get(); - return (inquiry && inquiry.status === 'queued') || !room.servedBy; + return inquiry && inquiry.status === 'queued'; }, roomOpen() { @@ -54,15 +55,29 @@ Template.livechatReadOnly.events({ await callWithErrorHandling('livechat:resumeOnHold', room._id, { clientAction: true }); }, + + async 'click .js-join-it'(event) { + event.preventDefault(); + event.stopPropagation(); + + try { + const { success } = (await APIClient.v1.get(`livechat/room.join?roomId=${this.rid}`)) || {}; + if (!success) { + throw new Meteor.Error('error-join-room', 'Error joining room'); + } + } catch (error) { + handleError(error); + throw error; + } + }, }); -Template.livechatReadOnly.onCreated(function () { +Template.livechatReadOnly.onCreated(async function () { this.rid = Template.currentData().rid; this.room = new ReactiveVar(); this.inquiry = new ReactiveVar(); this.routingConfig = new ReactiveVar({}); this.preparing = new ReactiveVar(true); - this.updateInquiry = async ({ clientAction, ...inquiry }) => { if (clientAction === 'removed') { // this will force to refresh the room @@ -82,20 +97,24 @@ Template.livechatReadOnly.onCreated(function () { } }); - this.loadInquiry = async (roomId) => { + this.loadRoomAndInquiry = async (roomId) => { this.preparing.set(true); const { inquiry } = await APIClient.v1.get(`livechat/inquiries.getOne?roomId=${roomId}`); this.inquiry.set(inquiry); if (inquiry && inquiry._id) { inquiryDataStream.on(inquiry._id, this.updateInquiry); } + + const { room } = await APIClient.v1.get(`rooms.info?roomId=${roomId}`); + this.room.set(room); + if (room && room._id) { + RoomManager.roomStream.on(roomId, (room) => this.room.set(room)); + } + this.preparing.set(false); }; - this.autorun(() => this.loadInquiry(this.rid)); - this.autorun(() => { - this.room.set(ChatRoom.findOne({ _id: Template.currentData().rid }, { fields: { open: 1, servedBy: 1 } })); - }); + this.autorun(() => this.loadRoomAndInquiry(this.rid)); }); Template.livechatReadOnly.onDestroyed(function () { @@ -103,4 +122,7 @@ Template.livechatReadOnly.onDestroyed(function () { if (inquiry && inquiry._id) { inquiryDataStream.removeListener(inquiry._id, this.updateInquiry); } + + const { rid } = Template.currentData(); + RoomManager.roomStream.removeListener(rid); }); diff --git a/app/livechat/client/views/app/tabbar/visitorEdit.js b/app/livechat/client/views/app/tabbar/visitorEdit.js index ae9d448ef157..ce70d5e979b8 100644 --- a/app/livechat/client/views/app/tabbar/visitorEdit.js +++ b/app/livechat/client/views/app/tabbar/visitorEdit.js @@ -3,7 +3,7 @@ import { ReactiveVar } from 'meteor/reactive-var'; import { Template } from 'meteor/templating'; import { t } from '../../../../../utils'; -import { hasAtLeastOnePermission, hasPermission, hasRole } from '../../../../../authorization/client'; +import { hasAtLeastOnePermission, hasPermission, hasAnyRole } from '../../../../../authorization/client'; import './visitorEdit.html'; import { APIClient } from '../../../../../utils/client'; import { getCustomFormTemplate } from '../customTemplates/register'; @@ -87,7 +87,7 @@ Template.visitorEdit.helpers({ canRemoveTag(availableUserTags, tag) { return ( - hasRole(Meteor.userId(), ['admin', 'livechat-manager']) || + hasAnyRole(Meteor.userId(), ['admin', 'livechat-manager']) || (Array.isArray(availableUserTags) && (availableUserTags.length === 0 || availableUserTags.indexOf(tag) > -1)) ); }, @@ -136,7 +136,7 @@ Template.visitorEdit.onCreated(async function () { Meteor.call('livechat:getTagsList', (err, tagsList) => { this.availableTags.set(tagsList); const agentDepartments = this.agentDepartments.get(); - const isAdmin = hasRole(uid, ['admin', 'livechat-manager']); + const isAdmin = hasAnyRole(uid, ['admin', 'livechat-manager']); const tags = this.availableTags.get() || []; const availableTags = tags .filter(({ departments }) => isAdmin || departments.length === 0 || departments.some((i) => agentDepartments.indexOf(i) > -1)) @@ -200,7 +200,7 @@ Template.visitorEdit.events({ const hasAvailableTags = availableTags && availableTags.length > 0; const availableUserTags = t.availableUserTags.get(); if ( - !hasRole(Meteor.userId(), ['admin', 'livechat-manager']) && + !hasAnyRole(Meteor.userId(), ['admin', 'livechat-manager']) && hasAvailableTags && (!availableUserTags || availableUserTags.indexOf(tag) === -1) ) { diff --git a/app/livechat/client/views/app/tabbar/visitorForward.js b/app/livechat/client/views/app/tabbar/visitorForward.js index 6853c58e40a7..b591ed84e52d 100644 --- a/app/livechat/client/views/app/tabbar/visitorForward.js +++ b/app/livechat/client/views/app/tabbar/visitorForward.js @@ -99,7 +99,7 @@ Template.visitorForward.onCreated(async function () { } }); - const { departments } = await APIClient.v1.get('livechat/department'); + const { departments } = await APIClient.v1.get('livechat/department?enabled=true'); this.departments.set(departments); }); diff --git a/app/livechat/client/views/app/tabbar/visitorInfo.js b/app/livechat/client/views/app/tabbar/visitorInfo.js index aebae6ef9221..4a5d71e94c14 100644 --- a/app/livechat/client/views/app/tabbar/visitorInfo.js +++ b/app/livechat/client/views/app/tabbar/visitorInfo.js @@ -11,7 +11,7 @@ import UAParser from 'ua-parser-js'; import { modal } from '../../../../../ui-utils'; import { Subscriptions } from '../../../../../models'; import { settings } from '../../../../../settings'; -import { t, roomTypes } from '../../../../../utils'; +import { t } from '../../../../../utils'; import { hasRole, hasPermission, hasAtLeastOnePermission } from '../../../../../authorization'; import './visitorInfo.html'; import { APIClient } from '../../../../../utils/client'; @@ -20,6 +20,7 @@ import { getCustomFormTemplate } from '../customTemplates/register'; import { Markdown } from '../../../../../markdown/client'; import { handleError } from '../../../../../../client/lib/utils/handleError'; import { formatDateAndTime } from '../../../../../../client/lib/utils/formatDateAndTime'; +import { roomCoordinator } from '../../../../../../client/lib/rooms/roomCoordinator'; const isSubscribedToRoom = () => { const data = Template.currentData(); @@ -55,7 +56,7 @@ Template.visitorInfo.helpers({ user.browser = `${ua.getBrowser().name} ${ua.getBrowser().version}`; user.browserIcon = `icon-${ua.getBrowser().name.toLowerCase()}`; - user.status = roomTypes.getUserStatus('l', this.rid) || 'offline'; + user.status = roomCoordinator.getRoomDirectives('l')?.getUserStatus(this.rid) || 'offline'; } return user; }, diff --git a/app/livechat/client/views/app/tabbar/visitorTranscript.js b/app/livechat/client/views/app/tabbar/visitorTranscript.js index c544dccdca01..de70f61347f2 100644 --- a/app/livechat/client/views/app/tabbar/visitorTranscript.js +++ b/app/livechat/client/views/app/tabbar/visitorTranscript.js @@ -4,10 +4,11 @@ import { Template } from 'meteor/templating'; import { dispatchToastMessage } from '../../../../../../client/lib/toast'; import { handleError } from '../../../../../../client/lib/utils/handleError'; -import { t, roomTypes } from '../../../../../utils'; +import { t } from '../../../../../utils'; import { APIClient } from '../../../../../utils/client'; import './visitorTranscript.html'; import { validateEmail } from '../../../../../../lib/emailValidator'; +import { roomCoordinator } from '../../../../../../client/lib/rooms/roomCoordinator'; const validateTranscriptData = (instance) => { const subject = instance.$('[name="subject"]').val(); @@ -63,7 +64,7 @@ Template.visitorTranscript.helpers({ return room.transcriptRequest.subject; } - return t('Transcript_of_your_livechat_conversation') || (room && roomTypes.getRoomName(room.t, room)); + return t('Transcript_of_your_livechat_conversation') || (room && roomCoordinator.getRoomName(room.t, room)); }, errorEmail() { const instance = Template.instance(); diff --git a/app/livechat/client/voip.ts b/app/livechat/client/voip.ts new file mode 100644 index 000000000000..003914740dca --- /dev/null +++ b/app/livechat/client/voip.ts @@ -0,0 +1,71 @@ +import moment from 'moment'; + +import { MessageTypes, IMessageType } from '../../ui-utils/client'; +import { IMessage, isVoipMessage } from '../../../definition/IMessage'; + +type IMessageFuncReturn = { comment: string } | { duration: string } | { reason: string }; + +const messageTypes: IMessageType[] = [ + { + id: 'voip-call-started', + system: true, + message: 'Voip_call_started', + }, + { + id: 'voip-call-duration', + system: true, + message: 'Voip_call_duration', + data(message: IMessage): IMessageFuncReturn { + if (!isVoipMessage(message)) { + return { duration: '' }; + } + const seconds = (message.voipData.callDuration || 0) / 1000; + const duration = moment.duration(seconds, 'seconds').humanize(); + return { + duration, + }; + }, + }, + { + id: 'voip-call-declined', + system: true, + message: 'Voip_call_declined', + }, + { + id: 'voip-call-on-hold', + system: true, + message: 'Voip_call_on_hold', + }, + { + id: 'voip-call-unhold', + system: true, + message: 'Voip_call_unhold', + }, + { + id: 'voip-call-ended', + system: true, + message: 'Voip_call_ended', + }, + { + id: 'voip-call-ended-unexpectedly', + system: true, + message: 'Voip_call_ended_unexpectedly', + data(message: IMessage): IMessageFuncReturn { + return { + reason: message.msg, + }; + }, + }, + { + id: 'voip-call-wrapup', + system: true, + message: 'Voip_call_wrapup', + data(message: IMessage): IMessageFuncReturn { + return { + comment: message.msg, + }; + }, + }, +]; + +messageTypes.map((e) => MessageTypes.registerType(e)); diff --git a/app/livechat/imports/server/rest/inquiries.js b/app/livechat/imports/server/rest/inquiries.js index 41f6c421ea37..09c92c681a15 100644 --- a/app/livechat/imports/server/rest/inquiries.js +++ b/app/livechat/imports/server/rest/inquiries.js @@ -5,6 +5,7 @@ import { API } from '../../../../api/server'; import { hasPermission } from '../../../../authorization'; import { Users, LivechatDepartment, LivechatInquiry } from '../../../../models'; import { findInquiries, findOneInquiryByRoomId } from '../../../server/api/lib/inquiries'; +import { LivechatInquiryStatus } from '../../../../../definition/IInquiry'; API.v1.addRoute( 'livechat/inquiries.list', @@ -101,6 +102,31 @@ API.v1.addRoute( }, ); +API.v1.addRoute( + 'livechat/inquiries.queuedForUser', + { authRequired: true }, + { + async get() { + const { offset, count } = this.getPaginationItems(); + const { sort } = this.parseJsonQuery(); + const { department } = this.requestParams(); + + return API.v1.success( + await findInquiries({ + userId: this.userId, + filterDepartment: department, + status: LivechatInquiryStatus.QUEUED, + pagination: { + offset, + count, + sort, + }, + }), + ); + }, + }, +); + API.v1.addRoute( 'livechat/inquiries.getOne', { authRequired: true }, diff --git a/app/livechat/imports/server/rest/sms.js b/app/livechat/imports/server/rest/sms.js index 8d2cdf0436d1..156ad3f3b7ce 100644 --- a/app/livechat/imports/server/rest/sms.js +++ b/app/livechat/imports/server/rest/sms.js @@ -1,10 +1,10 @@ -import { fetch } from 'meteor/fetch'; import { Meteor } from 'meteor/meteor'; import { Random } from 'meteor/random'; import { FileUpload } from '../../../../file-upload/server'; import { LivechatRooms, LivechatVisitors, LivechatDepartment } from '../../../../models/server'; import { API } from '../../../../api/server'; +import { fetch } from '../../../../../server/lib/http/fetch'; import { SMS } from '../../../../sms'; import { Livechat } from '../../../server/lib/Livechat'; import { OmnichannelSourceType } from '../../../../../definition/IRoom'; diff --git a/app/livechat/lib/LivechatRoomType.js b/app/livechat/lib/LivechatRoomType.js deleted file mode 100644 index 2ce5f2328a3e..000000000000 --- a/app/livechat/lib/LivechatRoomType.js +++ /dev/null @@ -1,137 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { Session } from 'meteor/session'; - -import { ChatRoom } from '../../models'; -import { settings } from '../../settings'; -import { hasPermission } from '../../authorization'; -import { openRoom } from '../../ui-utils'; -import { RoomMemberActions, RoomSettingsEnum, UiTextContext, RoomTypeRouteConfig, RoomTypeConfig } from '../../utils'; -import { getAvatarURL } from '../../utils/lib/getAvatarURL'; - -let LivechatInquiry; -if (Meteor.isClient) { - ({ LivechatInquiry } = require('../client/collections/LivechatInquiry')); -} - -class LivechatRoomRoute extends RoomTypeRouteConfig { - constructor() { - super({ - name: 'live', - path: '/live/:id/:tab?/:context?', - }); - } - - action(params) { - openRoom('l', params.id); - } - - link(sub) { - return { - id: sub.rid, - }; - } -} - -export default class LivechatRoomType extends RoomTypeConfig { - constructor() { - super({ - identifier: 'l', - order: 5, - icon: 'omnichannel', - label: 'Omnichannel', - route: new LivechatRoomRoute(), - }); - - this.notSubscribedTpl = 'livechatNotSubscribed'; - this.readOnlyTpl = 'livechatReadOnly'; - } - - enableMembersListProfile() { - return true; - } - - findRoom(identifier) { - return ChatRoom.findOne({ _id: identifier }); - } - - roomName(roomData) { - return roomData.name || roomData.fname || roomData.label; - } - - condition() { - return settings.get('Livechat_enabled') && hasPermission('view-l-room'); - } - - canSendMessage(rid) { - const room = ChatRoom.findOne({ _id: rid }, { fields: { open: 1 } }); - return room && room.open === true; - } - - getUserStatus(rid) { - const room = Session.get(`roomData${rid}`); - if (room) { - return room.v && room.v.status; - } - const inquiry = LivechatInquiry.findOne({ rid }); - return inquiry && inquiry.v && inquiry.v.status; - } - - allowRoomSettingChange(room, setting) { - switch (setting) { - case RoomSettingsEnum.JOIN_CODE: - return false; - default: - return true; - } - } - - allowMemberAction(room, action) { - return [RoomMemberActions.INVITE, RoomMemberActions.JOIN].includes(action); - } - - getUiText(context) { - switch (context) { - case UiTextContext.HIDE_WARNING: - return 'Hide_Livechat_Warning'; - case UiTextContext.LEAVE_WARNING: - return 'Hide_Livechat_Warning'; - default: - return ''; - } - } - - readOnly(rid) { - const room = ChatRoom.findOne({ _id: rid }, { fields: { open: 1, servedBy: 1 } }); - if (!room || !room.open) { - return true; - } - - const inquiry = LivechatInquiry.findOne({ rid }, { fields: { status: 1 } }); - if (inquiry && inquiry.status === 'queued') { - return true; - } - - return !room.servedBy; - } - - getAvatarPath(roomData) { - return getAvatarURL({ username: `@${this.roomName(roomData)}` }); - } - - openCustomProfileTab(instance, room, username) { - if (!room || !room.v || room.v.username !== username) { - return false; - } - - instance.tabBar.openUserInfo(); - return true; - } - - showQuickActionButtons() { - return true; - } - - isLivechatRoom() { - return true; - } -} diff --git a/app/livechat/server/api/lib/inquiries.js b/app/livechat/server/api/lib/inquiries.js index b83f86317547..2b113433fe0c 100644 --- a/app/livechat/server/api/lib/inquiries.js +++ b/app/livechat/server/api/lib/inquiries.js @@ -1,6 +1,5 @@ import { hasPermissionAsync } from '../../../../authorization/server/functions/hasPermission'; import { LivechatDepartmentAgents, LivechatDepartment, LivechatInquiry } from '../../../../models/server/raw'; -import { hasAnyRoleAsync } from '../../../../authorization/server/functions/hasRole'; const agentDepartments = async (userId) => { const agentDepartments = (await LivechatDepartmentAgents.findByAgentId(userId).toArray()).map(({ departmentId }) => departmentId); @@ -8,10 +7,6 @@ const agentDepartments = async (userId) => { }; const applyDepartmentRestrictions = async (userId, filterDepartment) => { - if (await hasAnyRoleAsync(userId, ['livechat-manager'])) { - return filterDepartment; - } - const allowedDepartments = await agentDepartments(userId); if (allowedDepartments && Array.isArray(allowedDepartments) && allowedDepartments.length > 0) { if (!filterDepartment) { @@ -48,6 +43,8 @@ export async function findInquiries({ userId, department: filterDepartment, stat $and: [{ defaultAgent: { $exists: true } }, { 'defaultAgent.agentId': userId }], }, { ...(department && { department }) }, + // Add _always_ the "public queue" to returned list of inquiries, even if agent already has departments + { department: { $exists: false } }, ], }; diff --git a/app/livechat/server/api/lib/users.js b/app/livechat/server/api/lib/users.js index 5b83f7c21205..b7c5e1e11bd7 100644 --- a/app/livechat/server/api/lib/users.js +++ b/app/livechat/server/api/lib/users.js @@ -3,6 +3,11 @@ import { escapeRegExp } from '@rocket.chat/string-helpers'; import { hasAllPermissionAsync } from '../../../../authorization/server/functions/hasPermission'; import { Users } from '../../../../models/server/raw'; +/** + * @param {IRole['_id']} role the role id + * @param {string} text + * @param {any} pagination + */ async function findUsers({ role, text, pagination: { offset, count, sort } }) { const query = {}; if (text) { diff --git a/app/livechat/server/api/v1/room.js b/app/livechat/server/api/v1/room.js index d29dbb6d5002..4d14f9977676 100644 --- a/app/livechat/server/api/v1/room.js +++ b/app/livechat/server/api/v1/room.js @@ -11,6 +11,8 @@ import { Livechat } from '../../lib/Livechat'; import { normalizeTransferredByData } from '../../lib/Helper'; import { findVisitorInfo } from '../lib/visitors'; import { OmnichannelSourceType } from '../../../../../definition/IRoom'; +import { canAccessRoom } from '../../../../authorization/server'; +import { addUserToRoom } from '../../../../lib/server/functions'; API.v1.addRoute('livechat/room', { get() { @@ -243,3 +245,39 @@ API.v1.addRoute( }, }, ); + +API.v1.addRoute( + 'livechat/room.join', + { authRequired: true }, + { + get() { + try { + check(this.queryParams, { roomId: String }); + + const { roomId } = this.queryParams; + + const { user } = this; + + if (!user) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'joinRoom' }); + } + + const room = LivechatRooms.findOneById(roomId); + + if (!room) { + throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'joinRoom' }); + } + + if (!canAccessRoom(room, user)) { + throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'joinRoom' }); + } + + addUserToRoom(roomId, user); + + return API.v1.success(); + } catch (e) { + return API.v1.failure(e); + } + }, + }, +); diff --git a/app/livechat/server/api/v1/videoCall.js b/app/livechat/server/api/v1/videoCall.js index 484eb30a0ba6..8f8f74c078e5 100644 --- a/app/livechat/server/api/v1/videoCall.js +++ b/app/livechat/server/api/v1/videoCall.js @@ -10,6 +10,9 @@ import { findGuest, getRoom, settings } from '../lib/livechat'; import { OmnichannelSourceType } from '../../../../../definition/IRoom'; import { hasPermission, canSendMessage } from '../../../../authorization'; import { Livechat } from '../../lib/Livechat'; +import { Logger } from '../../../../logger'; + +const logger = new Logger('LivechatVideoCallApi'); API.v1.addRoute('livechat/video.call/:token', { get() { @@ -62,6 +65,7 @@ API.v1.addRoute('livechat/video.call/:token', { return API.v1.success(this.deprecationWarning({ videoCall })); } catch (e) { + logger.error(e); return API.v1.failure(e); } }, @@ -124,6 +128,7 @@ API.v1.addRoute( }; return API.v1.success({ videoCall }); } catch (e) { + logger.error(e); return API.v1.failure(e); } }, @@ -170,6 +175,7 @@ API.v1.addRoute( return API.v1.success({ status }); } catch (e) { + logger.error(e); return API.v1.failure(e); } }, diff --git a/app/livechat/server/api/v1/visitor.ts b/app/livechat/server/api/v1/visitor.ts index 184f94e5034f..0ae7480dded1 100644 --- a/app/livechat/server/api/v1/visitor.ts +++ b/app/livechat/server/api/v1/visitor.ts @@ -8,6 +8,7 @@ import { findGuest, normalizeHttpHeaderData } from '../lib/livechat'; import { Livechat } from '../../lib/Livechat'; import { ILivechatVisitorDTO } from '../../../../../definition/ILivechatVisitor'; import { IRoom } from '../../../../../definition/IRoom'; +import { settings } from '../../../../settings/server'; API.v1.addRoute('livechat/visitor', { async post() { @@ -37,13 +38,15 @@ API.v1.addRoute('livechat/visitor', { } guest.connectionData = normalizeHttpHeaderData(this.request.headers); - const visitorId = Livechat.registerGuest(guest); + const visitorId = Livechat.registerGuest(guest as any); // TODO: Rewrite Livechat to TS - let visitor = await VisitorsRaw.getVisitorByToken(token, {}); + let visitor = await VisitorsRaw.findOneById(visitorId, {}); // If it's updating an existing visitor, it must also update the roomInfo - const cursor = LivechatRooms.findOpenByVisitorToken(token); + const cursor = LivechatRooms.findOpenByVisitorToken(visitor?.token); cursor.forEach((room: IRoom) => { - Livechat.saveRoomInfo(room, visitor); + if (visitor) { + Livechat.saveRoomInfo(room, visitor); + } }); if (customFields && customFields instanceof Array) { @@ -104,7 +107,8 @@ API.v1.addRoute('livechat/visitor/:token', { }, }).fetch(); - if (rooms?.length) { + // if gdpr is enabled, bypass rooms check + if (rooms?.length && !settings.get('Livechat_Allow_collect_and_store_HTTP_header_informations')) { throw new Meteor.Error('visitor-has-open-rooms', 'Cannot remove visitors with opened rooms'); } diff --git a/app/livechat/server/config.ts b/app/livechat/server/config.ts index 064713e3f668..b0327548ee41 100644 --- a/app/livechat/server/config.ts +++ b/app/livechat/server/config.ts @@ -723,7 +723,7 @@ Meteor.startup(function () { alert: 'Force_visitor_to_accept_data_processing_consent_enabled_alert', i18nLabel: 'Force_visitor_to_accept_data_processing_consent', i18nDescription: 'Force_visitor_to_accept_data_processing_consent_description', - enableQuery: omnichannelEnabledQuery, + enableQuery: [omnichannelEnabledQuery, { _id: 'Livechat_Allow_collect_and_store_HTTP_header_informations', value: true }], }); this.add('Livechat_data_processing_consent_text', '', { @@ -734,7 +734,11 @@ Meteor.startup(function () { public: true, i18nLabel: 'Data_processing_consent_text', i18nDescription: 'Data_processing_consent_text_description', - enableQuery: [{ _id: 'Livechat_force_accept_data_processing_consent', value: true }, omnichannelEnabledQuery], + enableQuery: [ + { _id: 'Livechat_force_accept_data_processing_consent', value: true }, + { _id: 'Livechat_Allow_collect_and_store_HTTP_header_informations', value: true }, + omnichannelEnabledQuery, + ], }); this.add('Livechat_agent_leave_action', 'none', { diff --git a/app/livechat/server/index.js b/app/livechat/server/index.js index 06d11207d777..0233ad63b7f7 100644 --- a/app/livechat/server/index.js +++ b/app/livechat/server/index.js @@ -4,7 +4,6 @@ import './startup'; import './visitorStatus'; import './agentStatus'; import '../lib/messageTypes'; -import './roomType'; import './hooks/beforeCloseRoom'; import './hooks/beforeDelegateAgent'; import './hooks/leadCapture'; diff --git a/app/livechat/server/lib/Livechat.js b/app/livechat/server/lib/Livechat.js index 65e2d5e66385..d873c580b60a 100644 --- a/app/livechat/server/lib/Livechat.js +++ b/app/livechat/server/lib/Livechat.js @@ -30,7 +30,7 @@ import { LivechatInquiry, } from '../../../models/server'; import { Logger } from '../../../logger/server'; -import { addUserRoles, hasPermission, hasRole, removeUserFromRoles, canAccessRoom } from '../../../authorization/server'; +import { hasPermission, hasRole, canAccessRoom, roomAccessAttributes } from '../../../authorization/server'; import * as Mailer from '../../../mailer'; import { sendMessage } from '../../../lib/server/functions/sendMessage'; import { updateMessage } from '../../../lib/server/functions/updateMessage'; @@ -41,6 +41,8 @@ import { Apps, AppEvents } from '../../../apps/server'; import { businessHourManager } from '../business-hour'; import notifications from '../../../notifications/server/lib/Notifications'; import { Users as UsersRaw } from '../../../models/server/raw'; +import { addUserRoles } from '../../../../server/lib/roles/addUserRoles'; +import { removeUserFromRoles } from '../../../../server/lib/roles/removeUserFromRoles'; import { Notifications } from '../../../notifications'; const PDFMakePrinter = require('pdfmake/src/printer'); @@ -329,7 +331,7 @@ export const Livechat = { return true; }, - registerGuest({ id, token, name, email, department, phone, username, connectionData } = {}) { + registerGuest({ id, token, name, email, department, phone, username, connectionData, status = 'online' } = {}) { check(token, String); check(id, Match.Maybe(String)); @@ -339,6 +341,7 @@ export const Livechat = { const updateUser = { $set: { token, + status, ...(phone?.number ? { phone: [{ phoneNumber: phone.number }] } : {}), ...(name ? { name } : {}), }, @@ -373,6 +376,11 @@ export const Livechat = { if (user) { Livechat.logger.debug('Found matching user by token'); userId = user._id; + } else if (phone?.number && (existingUser = LivechatVisitors.findOneVisitorByPhone(phone.number))) { + Livechat.logger.debug('Found matching user by phone number'); + userId = existingUser._id; + // Don't change token when matching by phone number, use current visitor token + updateUser.$set.token = existingUser.token; } else if (email && (existingUser = LivechatVisitors.findOneGuestByEmailAddress(email))) { Livechat.logger.debug('Found matching user by email'); userId = existingUser._id; @@ -384,6 +392,7 @@ export const Livechat = { const userData = { username, + status, ts: new Date(), ...(id && { _id: id }), }; @@ -963,7 +972,7 @@ export const Livechat = { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'livechat:addAgent' }); } - if (addUserRoles(user._id, 'livechat-agent')) { + if (addUserRoles(user._id, ['livechat-agent'])) { Users.setOperator(user._id, true); this.setUserStatusLivechat(user._id, user.status !== 'offline' ? 'available' : 'not-available'); return user; @@ -983,7 +992,7 @@ export const Livechat = { }); } - if (addUserRoles(user._id, 'livechat-manager')) { + if (addUserRoles(user._id, ['livechat-manager'])) { return user; } @@ -1003,7 +1012,7 @@ export const Livechat = { const { _id } = user; - if (removeUserFromRoles(_id, 'livechat-agent')) { + if (removeUserFromRoles(_id, ['livechat-agent'])) { Users.setOperator(_id, false); Users.removeLivechatData(_id); this.setUserStatusLivechat(_id, 'not-available'); @@ -1025,7 +1034,7 @@ export const Livechat = { }); } - return removeUserFromRoles(user._id, 'livechat-manager'); + return removeUserFromRoles(user._id, ['livechat-manager']); }, removeGuest(_id) { @@ -1507,7 +1516,8 @@ export const Livechat = { throw new Error('error-not-authorized'); } - const room = Promise.await(LivechatRooms.findOneById(roomId, { _id: 1, t: 1 })); + const room = Promise.await(LivechatRooms.findOneById(roomId, { ...roomAccessAttributes, _id: 1, t: 1 })); + if (!room) { throw new Meteor.Error('invalid-room'); } diff --git a/app/livechat/server/lib/messageTypes.js b/app/livechat/server/lib/messageTypes.js index 91a9012991d4..dadb7e69594c 100644 --- a/app/livechat/server/lib/messageTypes.js +++ b/app/livechat/server/lib/messageTypes.js @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { actionLinks } from '../../../action-links/server'; -import { Notifications } from '../../../notifications/server'; +import { api } from '../../../../server/sdk/api'; import { Messages, LivechatRooms } from '../../../models/server'; import { settings } from '../../../settings/server'; import { Livechat } from './Livechat'; @@ -11,7 +11,7 @@ actionLinks.register('denyLivechatCall', function (message /* , params*/) { const user = Meteor.user(); Messages.createWithTypeRoomIdMessageAndUser('command', message.rid, 'endCall', user); - Notifications.notifyRoom(message.rid, 'deleteMessage', { _id: message._id }); + api.broadcast('notify.deleteMessage', message.rid, { _id: message._id }); const language = user.language || settings.get('Language') || 'en'; diff --git a/app/livechat/server/methods/takeInquiry.js b/app/livechat/server/methods/takeInquiry.js index 1759a8c5a63b..f6852ad32c46 100644 --- a/app/livechat/server/methods/takeInquiry.js +++ b/app/livechat/server/methods/takeInquiry.js @@ -16,7 +16,7 @@ Meteor.methods({ const inquiry = LivechatInquiry.findOneById(inquiryId); if (!inquiry || inquiry.status === 'taken') { - throw new Meteor.Error('error-not-allowed', 'Inquiry already taken', { + throw new Meteor.Error('error-inquiry-taken', 'Inquiry already taken', { method: 'livechat:takeInquiry', }); } @@ -25,7 +25,7 @@ Meteor.methods({ fields: { _id: 1, username: 1, roles: 1, status: 1, statusLivechat: 1 }, }); if (!userCanTakeInquiry(user)) { - throw new Meteor.Error('error-not-allowed', 'Not allowed', { + throw new Meteor.Error('error-agent-status-service-offline', 'Agent status is offline or Omnichannel service is not active', { method: 'livechat:takeInquiry', }); } diff --git a/app/livechat/server/roomAccessValidator.internalService.ts b/app/livechat/server/roomAccessValidator.internalService.ts index f7e94219b18c..244bbc39b44b 100644 --- a/app/livechat/server/roomAccessValidator.internalService.ts +++ b/app/livechat/server/roomAccessValidator.internalService.ts @@ -1,16 +1,15 @@ -import { ServiceClass } from '../../../server/sdk/types/ServiceClass'; +import { IUser } from '../../../definition/IUser'; import { IAuthorizationLivechat } from '../../../server/sdk/types/IAuthorizationLivechat'; +import { ServiceClassInternal } from '../../../server/sdk/types/ServiceClass'; import { validators } from './roomAccessValidator.compatibility'; -import { api } from '../../../server/sdk/api'; -import { IRoom } from '../../../definition/IRoom'; -import { IUser } from '../../../definition/IUser'; +import type { IOmnichannelRoom } from '../../../definition/IRoom'; -class AuthorizationLivechat extends ServiceClass implements IAuthorizationLivechat { +export class AuthorizationLivechat extends ServiceClassInternal implements IAuthorizationLivechat { protected name = 'authorization-livechat'; protected internal = true; - async canAccessRoom(room: Partial, user: Partial, extraData?: object): Promise { + async canAccessRoom(room: IOmnichannelRoom, user: Pick, extraData?: object): Promise { for (const validator of validators) { if (validator(room, user, extraData)) { return true; @@ -20,5 +19,3 @@ class AuthorizationLivechat extends ServiceClass implements IAuthorizationLivech return false; } } - -api.registerService(new AuthorizationLivechat()); diff --git a/app/livechat/server/roomType.js b/app/livechat/server/roomType.js deleted file mode 100644 index 83919203f004..000000000000 --- a/app/livechat/server/roomType.js +++ /dev/null @@ -1,35 +0,0 @@ -import { LivechatRooms, LivechatVisitors } from '../../models'; -import { roomTypes } from '../../utils'; -import LivechatRoomType from '../lib/LivechatRoomType'; - -class LivechatRoomTypeServer extends LivechatRoomType { - getMsgSender(senderId) { - return LivechatVisitors.findOneById(senderId); - } - - /** - * Returns details to use on notifications - * - * @param {object} room - * @param {object} user - * @param {string} notificationMessage - * @return {object} Notification details - */ - getNotificationDetails(room, user, notificationMessage) { - const title = `[Omnichannel] ${this.roomName(room)}`; - const text = notificationMessage; - - return { title, text }; - } - - canAccessUploadedFile({ rc_token, rc_rid } = {}) { - return rc_token && rc_rid && LivechatRooms.findOneOpenByRoomIdAndVisitorToken(rc_rid, rc_token); - } - - getReadReceiptsExtraData(message) { - const { token } = message; - return { token }; - } -} - -roomTypes.add(new LivechatRoomTypeServer()); diff --git a/app/livechat/server/startup.js b/app/livechat/server/startup.js index 42456ddb8ae5..14229e94948b 100644 --- a/app/livechat/server/startup.js +++ b/app/livechat/server/startup.js @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { Accounts } from 'meteor/accounts-base'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; -import { roomTypes } from '../../utils'; +import { roomCoordinator } from '../../../server/lib/rooms/roomCoordinator'; import { LivechatRooms } from '../../models'; import { callbacks } from '../../../lib/callbacks'; import { settings } from '../../settings/server'; @@ -16,7 +16,7 @@ import { RoutingManager } from './lib/RoutingManager'; import './roomAccessValidator.internalService'; Meteor.startup(async () => { - roomTypes.setRoomFind('l', (_id) => LivechatRooms.findOneById(_id)); + roomCoordinator.setRoomFind('l', (_id) => LivechatRooms.findOneById(_id)); callbacks.add( 'beforeLeaveRoom', diff --git a/app/mail-messages/server/methods/sendMail.js b/app/mail-messages/server/methods/sendMail.js index bca0353cdb5b..80f3df8a4073 100644 --- a/app/mail-messages/server/methods/sendMail.js +++ b/app/mail-messages/server/methods/sendMail.js @@ -1,21 +1,18 @@ import { Meteor } from 'meteor/meteor'; import { Mailer } from '../lib/Mailer'; -import { hasRole } from '../../../authorization/server'; +import { hasPermission } from '../../../authorization/server'; Meteor.methods({ 'Mailer.sendMail'(from, subject, body, dryrun, query) { const userId = Meteor.userId(); - if (!userId) { - throw new Meteor.Error('error-invalid-user', 'Invalid user', { - method: 'Mailer.sendMail', - }); - } - if (hasRole(userId, 'admin') !== true) { + + if (!userId || !hasPermission(userId, 'send-mail')) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'Mailer.sendMail', }); } + return Mailer.sendMail(from, subject, body, dryrun, query); }, }); diff --git a/app/message-mark-as-unread/client/actionButton.js b/app/message-mark-as-unread/client/actionButton.js index ad0438c490b4..cd7e080b033c 100644 --- a/app/message-mark-as-unread/client/actionButton.js +++ b/app/message-mark-as-unread/client/actionButton.js @@ -4,8 +4,8 @@ import { FlowRouter } from 'meteor/kadira:flow-router'; import { RoomManager, MessageAction } from '../../ui-utils'; import { messageArgs } from '../../ui-utils/client/lib/messageArgs'; import { ChatSubscription } from '../../models'; -import { roomTypes } from '../../utils/client'; import { handleError } from '../../../client/lib/utils/handleError'; +import { roomCoordinator } from '../../../client/lib/rooms/roomCoordinator'; Meteor.startup(() => { MessageAction.addButton({ @@ -30,7 +30,7 @@ Meteor.startup(() => { }); }, condition({ msg, u, room }) { - const isLivechatRoom = roomTypes.isLivechatRoom(room.t); + const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t); if (isLivechatRoom) { return false; } diff --git a/app/message-pin/client/actionButton.js b/app/message-pin/client/actionButton.js index 45eee6df8367..3e093a1954fe 100644 --- a/app/message-pin/client/actionButton.js +++ b/app/message-pin/client/actionButton.js @@ -8,9 +8,9 @@ import { messageArgs } from '../../ui-utils/client/lib/messageArgs'; import { settings } from '../../settings'; import { hasAtLeastOnePermission } from '../../authorization'; import { Rooms } from '../../models/client'; -import { roomTypes } from '../../utils/client'; import { handleError } from '../../../client/lib/utils/handleError'; import { dispatchToastMessage } from '../../../client/lib/toast'; +import { roomCoordinator } from '../../../client/lib/rooms/roomCoordinator'; Meteor.startup(function () { MessageAction.addButton({ @@ -31,7 +31,7 @@ Meteor.startup(function () { if (!settings.get('Message_AllowPinning') || msg.pinned || !subscription) { return false; } - const isLivechatRoom = roomTypes.isLivechatRoom(room.t); + const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t); if (isLivechatRoom) { return false; } diff --git a/app/message-pin/server/pinMessage.js b/app/message-pin/server/pinMessage.js index d0826e29f58d..07aa7885c7cb 100644 --- a/app/message-pin/server/pinMessage.js +++ b/app/message-pin/server/pinMessage.js @@ -5,7 +5,7 @@ import { settings } from '../../settings/server'; import { callbacks } from '../../../lib/callbacks'; import { isTheLastMessage } from '../../lib/server'; import { getUserAvatarURL } from '../../utils/lib/getUserAvatarURL'; -import { canAccessRoom, hasPermission } from '../../authorization/server'; +import { canAccessRoom, hasPermission, roomAccessAttributes } from '../../authorization/server'; import { Subscriptions, Messages, Users, Rooms } from '../../models'; const recursiveRemove = (msg, deep = 1) => { @@ -164,7 +164,7 @@ Meteor.methods({ }; originalMessage = callbacks.run('beforeSaveMessage', originalMessage); - const room = Rooms.findOneById(originalMessage.rid, { fields: { lastMessage: 1 } }); + const room = Rooms.findOneById(originalMessage.rid, { fields: { ...roomAccessAttributes, lastMessage: 1 } }); if (!canAccessRoom(room, { _id: Meteor.userId() })) { throw new Meteor.Error('not-authorized', 'Not Authorized', { method: 'unpinMessage' }); } diff --git a/app/message-star/client/actionButton.js b/app/message-star/client/actionButton.js index 52c744798487..f47a754c80ca 100644 --- a/app/message-star/client/actionButton.js +++ b/app/message-star/client/actionButton.js @@ -7,9 +7,9 @@ import { settings } from '../../settings'; import { RoomHistoryManager, MessageAction } from '../../ui-utils'; import { messageArgs } from '../../ui-utils/client/lib/messageArgs'; import { Rooms } from '../../models/client'; -import { roomTypes } from '../../utils/client'; import { handleError } from '../../../client/lib/utils/handleError'; import { dispatchToastMessage } from '../../../client/lib/toast'; +import { roomCoordinator } from '../../../client/lib/rooms/roomCoordinator'; Meteor.startup(function () { MessageAction.addButton({ @@ -30,7 +30,7 @@ Meteor.startup(function () { if (subscription == null && settings.get('Message_AllowStarring')) { return false; } - const isLivechatRoom = roomTypes.isLivechatRoom(room.t); + const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t); if (isLivechatRoom) { return false; } @@ -70,7 +70,7 @@ Meteor.startup(function () { id: 'jump-to-star-message', icon: 'jump', label: 'Jump_to_message', - context: ['starred', 'threads', 'message-mobile'], + context: ['starred', 'message-mobile'], action() { const { msg: message } = messageArgs(this); if (window.matchMedia('(max-width: 500px)').matches) { diff --git a/app/message-star/server/starMessage.js b/app/message-star/server/starMessage.js index ae09e0c00d38..66c908524a15 100644 --- a/app/message-star/server/starMessage.js +++ b/app/message-star/server/starMessage.js @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { settings } from '../../settings/server'; import { isTheLastMessage } from '../../lib/server'; -import { canAccessRoom } from '../../authorization/server'; +import { canAccessRoom, roomAccessAttributes } from '../../authorization/server'; import { Subscriptions, Rooms, Messages } from '../../models/server'; Meteor.methods({ @@ -30,7 +30,8 @@ Meteor.methods({ return false; } - const room = Rooms.findOneById(message.rid, { fields: { lastMessage: 1 } }); + const room = Rooms.findOneById(message.rid, { fields: { ...roomAccessAttributes, lastMessage: 1 } }); + if (!canAccessRoom(room, { _id: Meteor.userId() })) { throw new Meteor.Error('not-authorized', 'Not Authorized', { method: 'starMessage' }); } diff --git a/app/meteor-accounts-saml/server/lib/SAML.ts b/app/meteor-accounts-saml/server/lib/SAML.ts index 63697c96f9d4..c5e2a792b652 100644 --- a/app/meteor-accounts-saml/server/lib/SAML.ts +++ b/app/meteor-accounts-saml/server/lib/SAML.ts @@ -19,6 +19,7 @@ import { ISAMLAction } from '../definition/ISAMLAction'; import { ISAMLUser } from '../definition/ISAMLUser'; import { SAMLUtils } from './Utils'; import { SystemLogger } from '../../../../server/lib/logger/system'; +import { ensureArray } from '../../../../lib/utils/arrayUtils'; const showErrorMessage = function (res: ServerResponse, err: string): void { res.writeHead(200, { @@ -127,7 +128,7 @@ export class SAML { if (!user) { // If we received any role from the mapping, use them - otherwise use the default role for creation. - const roles = userObject.roles?.length ? userObject.roles : SAMLUtils.ensureArray(defaultUserRole.split(',')); + const roles = userObject.roles?.length ? userObject.roles : ensureArray(defaultUserRole.split(',')); const newUser: Record = { name: userObject.fullName, @@ -218,7 +219,7 @@ export class SAML { ); if (username && username !== user.username) { - saveUserIdentity({ _id: user._id, username }); + saveUserIdentity({ _id: user._id, username } as Parameters[0]); } // sending token along with the userId diff --git a/app/meteor-accounts-saml/server/lib/Utils.ts b/app/meteor-accounts-saml/server/lib/Utils.ts index 7813b9da07f0..2461e4d73b6b 100644 --- a/app/meteor-accounts-saml/server/lib/Utils.ts +++ b/app/meteor-accounts-saml/server/lib/Utils.ts @@ -9,6 +9,7 @@ import { ISAMLGlobalSettings } from '../definition/ISAMLGlobalSettings'; import { IUserDataMap, IAttributeMapping } from '../definition/IAttributeMapping'; import { StatusCode } from './constants'; import { Logger } from '../../../../server/lib/logger/Logger'; +import { ensureArray } from '../../../../lib/utils/arrayUtils'; let providerList: Array = []; let debug = false; @@ -327,7 +328,7 @@ export class SAMLUtils { const values: Record = { regex: '', }; - const fieldNames = this.ensureArray(mapping.fieldName); + const fieldNames = ensureArray(mapping.fieldName); let mainValue; for (const fieldName of fieldNames) { @@ -406,11 +407,6 @@ export class SAMLUtils { return name; } - public static ensureArray(param: T | Array): Array { - const emptyArray: Array = []; - return emptyArray.concat(param); - } - public static mapProfileToUserObject(profile: Record): ISAMLUser { const userDataMap = this.getUserDataMapping(); SAMLUtils.log('parsed userDataMap', userDataMap); @@ -434,7 +430,7 @@ export class SAMLUtils { } const email = this.getProfileValue(profile, userDataMap.email); const profileUsername = this.getProfileValue(profile, userDataMap.username, true); - const name = this.getProfileValue(profile, userDataMap.name); + const name = this.getProfileValue(profile, userDataMap.name, true); // Even if we're not using the email to identify the user, it is still mandatory because it's a mandatory information on Rocket.Chat if (!email) { @@ -448,7 +444,7 @@ export class SAMLUtils { idpSession: profile.sessionIndex, nameID: profile.nameID, }, - emailList: this.ensureArray(email), + emailList: ensureArray(email), fullName: name || profile.displayName || profile.username, eppn: profile.eppn, attributeList, diff --git a/app/meteor-accounts-saml/server/lib/parsers/Response.ts b/app/meteor-accounts-saml/server/lib/parsers/Response.ts index ee1172b06489..bf92043a5976 100644 --- a/app/meteor-accounts-saml/server/lib/parsers/Response.ts +++ b/app/meteor-accounts-saml/server/lib/parsers/Response.ts @@ -403,7 +403,7 @@ export class ResponseParser { const key = attributes[i].getAttribute('Name'); if (key) { - SAMLUtils.log(`Name: ${attributes[i]}`); + SAMLUtils.log(`Attribute: ${attributes[i]} has ${values.length} value(s).`); SAMLUtils.log(`Adding attribute from SAML response to profile: ${key} = ${value}`); profile[key] = value; } diff --git a/app/models/client/models/Roles.js b/app/models/client/models/Roles.js index fa439cdd2a55..dc01f61c0661 100644 --- a/app/models/client/models/Roles.js +++ b/app/models/client/models/Roles.js @@ -6,20 +6,25 @@ import * as Models from '..'; const CachedRoles = new CachedCollection({ name: 'roles' }); const Roles = Object.assign(CachedRoles.collection, { - findUsersInRole(name, scope, options) { - const role = this.findOne(name); + findUsersInRole(roleId, scope, options) { + const role = this.findOne(roleId); const roleScope = (role && role.scope) || 'Users'; const model = Models[roleScope]; - return model && model.findUsersInRoles && model.findUsersInRoles(name, scope, options); + return model && model.findUsersInRoles && model.findUsersInRoles(roleId, scope, options); }, + /** + * @param {string} userId + * @param {IRole['_id'][]} roles the list of role ids + * @param {IRoom['_id']} scope the value for the role scope (room id) + */ isUserInRoles(userId, roles, scope) { roles = [].concat(roles); - return roles.some((roleName) => { - const role = this.findOne(roleName); + return roles.some((roleId) => { + const role = this.findOne(roleId); const roleScope = (role && role.scope) || 'Users'; const model = Models[roleScope]; - return model && model.isUserInRole && model.isUserInRole(userId, roleName, scope); + return model && model.isUserInRole && model.isUserInRole(userId, roleId, scope); }); }, diff --git a/app/models/client/models/Subscriptions.js b/app/models/client/models/Subscriptions.js index 5a2c0cc6358e..7e6785ce828a 100644 --- a/app/models/client/models/Subscriptions.js +++ b/app/models/client/models/Subscriptions.js @@ -7,7 +7,12 @@ const Subscriptions = {}; Object.assign(Subscriptions, { isUserInRole: mem( - function (userId, roleName, roomId) { + /** + * @param {string} userId + * @param {IRole['_id']} roleId + * @param {string} roomId + */ + function (userId, roleId, roomId) { if (roomId == null) { return false; } @@ -18,12 +23,17 @@ Object.assign(Subscriptions, { const subscription = this.findOne(query, { fields: { roles: 1 } }); - return subscription && Array.isArray(subscription.roles) && subscription.roles.includes(roleName); + return subscription && Array.isArray(subscription.roles) && subscription.roles.includes(roleId); }, { maxAge: 1000, cacheKey: JSON.stringify }, ), findUsersInRoles: mem( + /** + * @param {IRole['_id'][]} roles the list of role ids + * @param {string} scope the value for the role scope (room id) + * @param {any} options + */ function (roles, scope, options) { roles = [].concat(roles); diff --git a/app/models/client/models/Users.js b/app/models/client/models/Users.js index f0d53bbba4c4..a98bc35501e6 100644 --- a/app/models/client/models/Users.js +++ b/app/models/client/models/Users.js @@ -12,11 +12,20 @@ export const Users = { return this.findOne(query, options); }, - isUserInRole(userId, roleName) { + /** + * @param {string} userId + * @param {IRole['_id']} roleId + */ + isUserInRole(userId, roleId) { const user = this.findOneById(userId, { fields: { roles: 1 } }); - return user && Array.isArray(user.roles) && user.roles.includes(roleName); + return user && Array.isArray(user.roles) && user.roles.includes(roleId); }, + /** + * @param {IRole['_id'][]} roles the list of role ids + * @param {string} scope the value for the scope (room id) + * @param {any} options + */ findUsersInRoles(roles, scope, options) { roles = [].concat(roles); diff --git a/app/models/server/models/Messages.js b/app/models/server/models/Messages.js index aef83f788b3f..7ad0ae581c00 100644 --- a/app/models/server/models/Messages.js +++ b/app/models/server/models/Messages.js @@ -4,6 +4,7 @@ import _ from 'underscore'; import { Base } from './_Base'; import Rooms from './Rooms'; import { settings } from '../../../settings/server/functions/settings'; +import { otrSystemMessages } from '../../../otr/lib/constants'; export class Messages extends Base { constructor() { @@ -109,7 +110,18 @@ export class Messages extends Base { } deleteOldOTRMessages(roomId, ts) { - const query = { rid: roomId, t: 'otr', ts: { $lte: ts } }; + const query = { + rid: roomId, + t: { + $in: [ + 'otr', + otrSystemMessages.USER_JOINED_OTR, + otrSystemMessages.USER_REQUESTED_OTR_KEY_REFRESH, + otrSystemMessages.USER_KEY_REFRESHED_SUCCESSFULLY, + ], + }, + ts: { $lte: ts }, + }; return this.remove(query); } @@ -929,16 +941,46 @@ export class Messages extends Base { return this.createWithTypeRoomIdMessageAndUser('ult', roomId, message, user, extraData); } + createUserConvertChannelToTeamWithRoomIdAndUser(roomId, roomName, user, extraData) { + return this.createWithTypeRoomIdMessageAndUser('user-converted-to-team', roomId, roomName, user, extraData); + } + + createUserConvertTeamToChannelWithRoomIdAndUser(roomId, roomName, user, extraData) { + return this.createWithTypeRoomIdMessageAndUser('user-converted-to-channel', roomId, roomName, user, extraData); + } + + createUserRemoveRoomFromTeamWithRoomIdAndUser(roomId, roomName, user, extraData) { + return this.createWithTypeRoomIdMessageAndUser('user-removed-room-from-team', roomId, roomName, user, extraData); + } + + createUserDeleteRoomFromTeamWithRoomIdAndUser(roomId, roomName, user, extraData) { + return this.createWithTypeRoomIdMessageAndUser('user-deleted-room-from-team', roomId, roomName, user, extraData); + } + + createUserAddRoomToTeamWithRoomIdAndUser(roomId, roomName, user, extraData) { + return this.createWithTypeRoomIdMessageAndUser('user-added-room-to-team', roomId, roomName, user, extraData); + } + createUserRemovedWithRoomIdAndUser(roomId, user, extraData) { const message = user.username; return this.createWithTypeRoomIdMessageAndUser('ru', roomId, message, user, extraData); } + createUserRemovedFromTeamWithRoomIdAndUser(roomId, user, extraData) { + const message = user.username; + return this.createWithTypeRoomIdMessageAndUser('removed-user-from-team', roomId, message, user, extraData); + } + createUserAddedWithRoomIdAndUser(roomId, user, extraData) { const message = user.username; return this.createWithTypeRoomIdMessageAndUser('au', roomId, message, user, extraData); } + createUserAddedToTeamWithRoomIdAndUser(roomId, user, extraData) { + const message = user.username; + return this.createWithTypeRoomIdMessageAndUser('added-user-to-team', roomId, message, user, extraData); + } + createCommandWithRoomIdAndUser(command, roomId, user, extraData) { return this.createWithTypeRoomIdMessageAndUser('command', roomId, command, user, extraData); } @@ -993,6 +1035,11 @@ export class Messages extends Base { return this.createWithTypeRoomIdMessageAndUser('subscription-role-removed', roomId, message, user, extraData); } + createOtrSystemMessagesWithRoomIdAndUser(roomId, user, id, extraData) { + const message = user.username; + return this.createWithTypeRoomIdMessageAndUser(id, roomId, message, user, extraData); + } + // REMOVE removeById(_id) { const query = { _id }; diff --git a/app/models/server/models/Rooms.js b/app/models/server/models/Rooms.js index 64cc36b9ba07..bc8932afe570 100644 --- a/app/models/server/models/Rooms.js +++ b/app/models/server/models/Rooms.js @@ -25,6 +25,7 @@ export class Rooms extends Base { this.tryEnsureIndex({ fname: 1 }, { sparse: true }); // field used for DMs only this.tryEnsureIndex({ uids: 1 }, { sparse: true }); + this.tryEnsureIndex({ createdOTR: 1 }, { sparse: true }); this.tryEnsureIndex( { @@ -250,10 +251,7 @@ export class Rooms extends Base { return this.update({ _id }, update); } - setReadOnlyById(_id, readOnly, hasPermission) { - if (!hasPermission) { - throw new Error('You must provide "hasPermission" function to be able to call this method'); - } + setReadOnlyById(_id, readOnly) { const query = { _id, }; @@ -884,6 +882,10 @@ export class Rooms extends Base { ); } + findByCreatedOTR() { + return this.find({ createdOTR: true }); + } + // UPDATE addImportIds(_id, importIds) { importIds = [].concat(importIds); @@ -1464,6 +1466,18 @@ export class Rooms extends Base { countDiscussions() { return this.find({ prid: { $exists: true } }).count(); } + + setOTRForDMByRoomID(rid) { + const query = { _id: rid, t: 'd' }; + + const update = { + $set: { + createdOTR: true, + }, + }; + + return this.update(query, update); + } } export default new Rooms('room', true); diff --git a/app/models/server/models/Settings.js b/app/models/server/models/Settings.js index 3e2e57a2990a..19d7059e76a7 100644 --- a/app/models/server/models/Settings.js +++ b/app/models/server/models/Settings.js @@ -41,12 +41,6 @@ export class Settings extends Base { return this.find(query); } - findByRole(role, options) { - const query = { role }; - - return this.find(query, options); - } - findPublic(options) { const query = { public: true }; @@ -151,6 +145,21 @@ export class Settings extends Base { return this.update(query, update); } + incrementValueById(_id) { + const query = { + blocked: { $ne: true }, + _id, + }; + + const update = { + $inc: { + value: 1, + }, + }; + + return this.update(query, update); + } + updateValueAndEditorById(_id, value, editor) { const query = { blocked: { $ne: true }, diff --git a/app/models/server/models/Subscriptions.js b/app/models/server/models/Subscriptions.js index deddc06afc5a..5286c2bab955 100644 --- a/app/models/server/models/Subscriptions.js +++ b/app/models/server/models/Subscriptions.js @@ -108,6 +108,10 @@ export class Subscriptions extends Base { return this.distinct('autoTranslateLanguage', query); } + /** + * @param {string} userId + * @param {string} scope the value for the role scope (room id) + */ roleBaseQuery(userId, scope) { if (scope == null) { return; @@ -329,6 +333,11 @@ export class Subscriptions extends Base { return this.findOne({ _id }); } + /** + * @param {IRole['_id'][]} roles + * @param {string} scope the value for the role scope (room id) + * @param {any} options + */ findUsersInRoles(roles, scope, options) { roles = [].concat(roles); @@ -428,6 +437,11 @@ export class Subscriptions extends Base { return this.find(query, options); } + /** + * @param {IUser['_id']} userId + * @param {IRole['_id'][]} roles + * @param {any} options + */ findByUserIdAndRoles(userId, roles, options) { const query = { 'u._id': userId, @@ -448,7 +462,12 @@ export class Subscriptions extends Base { return this.find(query, options); } - findByRoomIdAndRoles(roomId, roles, options) { + /** + * @param {string} roomId + * @param {IRole['_id'][]} roles the list of roles + * @param {any} options + */ + findByRoomIdAndRoles(roomId, roles, options = undefined) { roles = [].concat(roles); const query = { rid: roomId, @@ -556,9 +575,6 @@ export class Subscriptions extends Base { return this.db.findOne( { rid, - ls: { - $exists: true, - }, }, { sort: { @@ -1028,6 +1044,10 @@ export class Subscriptions extends Base { return this.update(query, update, { multi: true }); } + /** + * @param {string} _id the subscription id + * @param {IRole['_id']} role the id of the role + */ addRoleById(_id, role) { const query = { _id }; @@ -1040,6 +1060,10 @@ export class Subscriptions extends Base { return this.update(query, update); } + /** + * @param {string} _id the subscription id + * @param {IRole['_id']} role the id of the role + */ removeRoleById(_id, role) { const query = { _id }; diff --git a/app/models/server/models/Users.js b/app/models/server/models/Users.js index bb4de2d1cd43..2f344afa0c2a 100644 --- a/app/models/server/models/Users.js +++ b/app/models/server/models/Users.js @@ -58,6 +58,7 @@ export class Users extends Base { this.tryEnsureIndex({ 'services.saml.inResponseTo': 1 }); this.tryEnsureIndex({ openBusinessHours: 1 }, { sparse: true }); this.tryEnsureIndex({ statusLivechat: 1 }, { sparse: true }); + this.tryEnsureIndex({ extension: 1 }, { sparse: true, unique: true }); this.tryEnsureIndex({ language: 1 }, { sparse: true }); const collectionObj = this.model.rawCollection(); @@ -637,6 +638,11 @@ export class Users extends Base { ); } + /** + * @param {IRole['_id'][]} roles the list of role ids + * @param {null} scope the value for the role scope (room id) - not used in the users collection + * @param {any} options + */ findUsersInRoles(roles, scope, options) { roles = [].concat(roles); @@ -647,7 +653,11 @@ export class Users extends Base { return this.find(query, options); } - findActiveUsersInRoles(roles, scope, options) { + /** + * @param {IRole['_id'][]} roles the list of role ids + * @param {any} options + */ + findActiveUsersInRoles(roles, options = undefined) { roles = [].concat(roles); const query = { @@ -767,6 +777,12 @@ export class Users extends Base { return this.findOne(query, options); } + findOneByRolesAndType(roles, type, options) { + const query = { roles, type }; + + return this.findOne(query, options); + } + // FIND findByIds(users, options) { const query = { _id: { $in: users } }; @@ -1322,6 +1338,11 @@ export class Users extends Base { return this.update({}, update, { multi: true }); } + /** + * @param latestLastLoginDate + * @param {IRole['_id']} role the role id + * @param {boolean} active + */ setActiveNotLoggedInAfterWithRole(latestLastLoginDate, role = 'user', active = false) { const neverActive = { lastLogin: { $exists: 0 }, createdAt: { $lte: latestLastLoginDate } }; const idleTooLong = { lastLogin: { $lte: latestLastLoginDate } }; diff --git a/app/models/server/models/_Base.js b/app/models/server/models/_Base.js index 1b8efd47311b..3ef8d4e56526 100644 --- a/app/models/server/models/_Base.js +++ b/app/models/server/models/_Base.js @@ -29,17 +29,26 @@ export class Base { return this.find(query, { fields: { roles: 1 } }); } - isUserInRole(userId, roleName, scope) { + /** + * @param {string} userId + * @param {IRole['_id']} roleId + * @param {string} scope the value for the role scope (room id) + */ + isUserInRole(userId, roleId, scope) { const query = this.roleBaseQuery(userId, scope); if (query == null) { return false; } - query.roles = roleName; + query.roles = roleId; return !_.isUndefined(this.findOne(query, { fields: { roles: 1 } })); } + /** + * @param {string} uid + * @param {string} scope the value for the role scope (room id) + */ isUserInRoleScope(uid, scope) { const query = this.roleBaseQuery(uid, scope); if (!query) { @@ -54,6 +63,11 @@ export class Base { return !!found; } + /** + * @param {string} userId + * @param {IRole['_id'][]} roles the list of role ids + * @param {string} scope the value for the role scope (room id) + */ addRolesByUserId(userId, roles, scope) { roles = [].concat(roles); const query = this.roleBaseQuery(userId, scope); @@ -65,6 +79,11 @@ export class Base { return this.update(query, update); } + /** + * @param {string} userId + * @param {IRole['_id'][]} roles the list of role ids + * @param {string} scope the value for the role scope (room id) + */ removeRolesByUserId(userId, roles, scope) { roles = [].concat(roles); const query = this.roleBaseQuery(userId, scope); diff --git a/app/models/server/models/_BaseDb.js b/app/models/server/models/_BaseDb.js index c93a0f5d809a..64bc40d081f8 100644 --- a/app/models/server/models/_BaseDb.js +++ b/app/models/server/models/_BaseDb.js @@ -8,6 +8,7 @@ import { setUpdatedAt } from '../lib/setUpdatedAt'; import { metrics } from '../../../metrics/server/lib/metrics'; import { getOplogHandle } from './_oplogHandle'; import { SystemLogger } from '../../../../server/lib/logger/system'; +import { isRunningMs } from '../../../../server/lib/isRunningMs'; const baseName = 'rocketchat_'; @@ -32,7 +33,7 @@ export class BaseDbWatch extends EventEmitter { super(); this.collectionName = collectionName; - if (!process.env.DISABLE_DB_WATCH) { + if (!isRunningMs()) { this.initDbWatch(); } } diff --git a/app/models/server/models/_oplogHandle.ts b/app/models/server/models/_oplogHandle.ts index 969e930c8049..98f0e5608329 100644 --- a/app/models/server/models/_oplogHandle.ts +++ b/app/models/server/models/_oplogHandle.ts @@ -5,6 +5,7 @@ import { MongoClient, Cursor, Timestamp, Db } from 'mongodb'; import { escapeRegExp } from '@rocket.chat/string-helpers'; import { urlParser } from './_oplogUrlParser'; +import { isRunningMs } from '../../../../server/lib/isRunningMs'; class CustomOplogHandle { dbName: string; @@ -206,7 +207,7 @@ class CustomOplogHandle { let oplogHandle: CustomOplogHandle; -if (!process.env.DISABLE_DB_WATCH) { +if (!isRunningMs()) { const disableOplog = !!(global.Package as any)['disable-oplog']; if (disableOplog) { @@ -219,10 +220,6 @@ if (!process.env.DISABLE_DB_WATCH) { } export const getOplogHandle = async (): Promise => { - if (process.env.DISABLE_DB_WATCH) { - return; - } - if (oplogHandle) { return oplogHandle; } diff --git a/app/models/server/raw/Analytics.ts b/app/models/server/raw/Analytics.ts index e78f219d2650..23a2b637d2f9 100644 --- a/app/models/server/raw/Analytics.ts +++ b/app/models/server/raw/Analytics.ts @@ -1,14 +1,16 @@ import { Random } from 'meteor/random'; import { AggregationCursor, Cursor, SortOptionObject, UpdateWriteOpResult } from 'mongodb'; -import { BaseRaw, IndexSpecification } from './BaseRaw'; +import { BaseRaw } from './BaseRaw'; import { IAnalytic } from '../../../../definition/IAnalytic'; import { IRoom } from '../../../../definition/IRoom'; type T = IAnalytic; export class AnalyticsRaw extends BaseRaw { - protected indexes: IndexSpecification[] = [{ key: { date: 1 } }, { key: { 'room._id': 1, 'date': 1 }, unique: true }]; + protected modelIndexes() { + return [{ key: { date: 1 } }, { key: { 'room._id': 1, 'date': 1 }, unique: true }]; + } saveMessageSent({ room, date }: { room: IRoom; date: IAnalytic['date'] }): Promise { return this.updateMany( diff --git a/app/models/server/raw/Avatars.ts b/app/models/server/raw/Avatars.ts index 33628d6eb66e..f092c34b8e67 100644 --- a/app/models/server/raw/Avatars.ts +++ b/app/models/server/raw/Avatars.ts @@ -1,13 +1,15 @@ import { DeleteWriteOpResultObject, UpdateWriteOpResult } from 'mongodb'; -import { BaseRaw, IndexSpecification } from './BaseRaw'; +import { BaseRaw } from './BaseRaw'; import { IAvatar as T } from '../../../../definition/IAvatar'; export class AvatarsRaw extends BaseRaw { - protected indexes: IndexSpecification[] = [ - { key: { name: 1 }, sparse: true }, - { key: { rid: 1 }, sparse: true }, - ]; + protected modelIndexes() { + return [ + { key: { name: 1 }, sparse: true }, + { key: { rid: 1 }, sparse: true }, + ]; + } insertAvatarFileInit(name: string, userId: string, store: string, file: { name: string }, extra: object): Promise { const fileData = { diff --git a/app/models/server/raw/Banners.ts b/app/models/server/raw/Banners.ts index c0285527d78d..6b1db0a94c38 100644 --- a/app/models/server/raw/Banners.ts +++ b/app/models/server/raw/Banners.ts @@ -1,16 +1,12 @@ -import { Collection, Cursor, FindOneOptions, UpdateWriteOpResult, WithoutProjection, InsertOneWriteOpResult } from 'mongodb'; +import { Cursor, FindOneOptions, UpdateWriteOpResult, WithoutProjection, InsertOneWriteOpResult } from 'mongodb'; import { BannerPlatform, IBanner } from '../../../../definition/IBanner'; import { BaseRaw } from './BaseRaw'; type T = IBanner; export class BannersRaw extends BaseRaw { - constructor(public readonly col: Collection, trash?: Collection) { - super(col, trash); - - this.col.createIndexes([{ key: { platform: 1, startAt: 1, expireAt: 1 } }]); - - this.col.createIndexes([{ key: { platform: 1, startAt: 1, expireAt: 1, active: 1 } }]); + protected modelIndexes() { + return [{ key: { platform: 1, startAt: 1, expireAt: 1 } }, { key: { platform: 1, startAt: 1, expireAt: 1, active: 1 } }]; } create(doc: IBanner): Promise> { diff --git a/app/models/server/raw/BannersDismiss.ts b/app/models/server/raw/BannersDismiss.ts index 61325e5423f3..007a7e155143 100644 --- a/app/models/server/raw/BannersDismiss.ts +++ b/app/models/server/raw/BannersDismiss.ts @@ -1,13 +1,11 @@ -import { Collection, Cursor, FindOneOptions, WithoutProjection } from 'mongodb'; +import { Cursor, FindOneOptions, WithoutProjection } from 'mongodb'; import { IBannerDismiss } from '../../../../definition/IBanner'; import { BaseRaw } from './BaseRaw'; export class BannersDismissRaw extends BaseRaw { - constructor(public readonly col: Collection, trash?: Collection) { - super(col, trash); - - this.col.createIndexes([{ key: { userId: 1, bannerId: 1 } }]); + modelIndexes() { + return [{ key: { userId: 1, bannerId: 1 } }]; } findByUserIdAndBannerId(userId: string, bannerIds: string[]): Cursor; diff --git a/app/models/server/raw/BaseRaw.ts b/app/models/server/raw/BaseRaw.ts index c4066eea7abc..90c0fcfc63df 100644 --- a/app/models/server/raw/BaseRaw.ts +++ b/app/models/server/raw/BaseRaw.ts @@ -73,8 +73,6 @@ const warnFields = export class BaseRaw = undefined> implements IBaseRaw { public readonly defaultFields: C; - protected indexes?: IndexSpecification[]; - protected name: string; private preventSetUpdatedAt: boolean; @@ -85,13 +83,18 @@ export class BaseRaw = undefined> implements IBase this.name = this.col.collectionName.replace(baseName, ''); this.trash = trash as unknown as Collection>; - if (this.indexes?.length) { - this.col.createIndexes(this.indexes); + const indexes = this.modelIndexes(); + if (indexes?.length) { + this.col.createIndexes(indexes); } this.preventSetUpdatedAt = options?.preventSetUpdatedAt ?? false; } + protected modelIndexes(): IndexSpecification[] | void { + // noop + } + private doNotMixInclusionAndExclusionFields(options: FindOneOptions = {}): FindOneOptions { const optionsDef = this.ensureDefaultFields(options); if (optionsDef?.projection === undefined) { diff --git a/app/models/server/raw/CredentialTokens.ts b/app/models/server/raw/CredentialTokens.ts index 930d00527165..9288eca4dac1 100644 --- a/app/models/server/raw/CredentialTokens.ts +++ b/app/models/server/raw/CredentialTokens.ts @@ -1,8 +1,10 @@ -import { BaseRaw, IndexSpecification } from './BaseRaw'; +import { BaseRaw } from './BaseRaw'; import { ICredentialToken as T } from '../../../../definition/ICredentialToken'; export class CredentialTokensRaw extends BaseRaw { - protected indexes: IndexSpecification[] = [{ key: { expireAt: 1 }, sparse: true, expireAfterSeconds: 0 }]; + protected modelIndexes() { + return [{ key: { expireAt: 1 }, sparse: true, expireAfterSeconds: 0 }]; + } async create(_id: string, userInfo: T['userInfo']): Promise { const validForMilliseconds = 60000; // Valid for 60 seconds diff --git a/app/models/server/raw/CustomSounds.ts b/app/models/server/raw/CustomSounds.ts index 5e7dd4572ac9..9dc83064da1f 100644 --- a/app/models/server/raw/CustomSounds.ts +++ b/app/models/server/raw/CustomSounds.ts @@ -1,10 +1,12 @@ import { Cursor, FindOneOptions, InsertOneWriteOpResult, UpdateWriteOpResult, WithId, WithoutProjection } from 'mongodb'; -import { BaseRaw, IndexSpecification } from './BaseRaw'; +import { BaseRaw } from './BaseRaw'; import { ICustomSound as T } from '../../../../definition/ICustomSound'; export class CustomSoundsRaw extends BaseRaw { - protected indexes: IndexSpecification[] = [{ key: { name: 1 } }]; + protected modelIndexes() { + return [{ key: { name: 1 } }]; + } // find findByName(name: string, options: WithoutProjection>): Cursor { diff --git a/app/models/server/raw/CustomUserStatus.ts b/app/models/server/raw/CustomUserStatus.ts index d4ed3321183d..8d342d67bbe0 100644 --- a/app/models/server/raw/CustomUserStatus.ts +++ b/app/models/server/raw/CustomUserStatus.ts @@ -1,10 +1,12 @@ import { Cursor, FindOneOptions, InsertOneWriteOpResult, UpdateWriteOpResult, WithId, WithoutProjection } from 'mongodb'; -import { BaseRaw, IndexSpecification } from './BaseRaw'; +import { BaseRaw } from './BaseRaw'; import { ICustomUserStatus as T } from '../../../../definition/ICustomUserStatus'; export class CustomUserStatusRaw extends BaseRaw { - protected indexes: IndexSpecification[] = [{ key: { name: 1 } }]; + protected modelIndexes() { + return [{ key: { name: 1 } }]; + } // find one by name async findOneByName(name: string, options: WithoutProjection>): Promise { diff --git a/app/models/server/raw/EmailInbox.ts b/app/models/server/raw/EmailInbox.ts index 38889a1d9072..dacb35b385ac 100644 --- a/app/models/server/raw/EmailInbox.ts +++ b/app/models/server/raw/EmailInbox.ts @@ -1,6 +1,8 @@ -import { BaseRaw, IndexSpecification } from './BaseRaw'; +import { BaseRaw } from './BaseRaw'; import { IEmailInbox } from '../../../../definition/IEmailInbox'; export class EmailInboxRaw extends BaseRaw { - protected indexes: IndexSpecification[] = [{ key: { email: 1 }, unique: true }]; + protected modelIndexes() { + return [{ key: { email: 1 }, unique: true }]; + } } diff --git a/app/models/server/raw/EmailMessageHistory.ts b/app/models/server/raw/EmailMessageHistory.ts index df97023387ea..df1315dc5525 100644 --- a/app/models/server/raw/EmailMessageHistory.ts +++ b/app/models/server/raw/EmailMessageHistory.ts @@ -1,10 +1,12 @@ -import { IndexSpecification, InsertOneWriteOpResult, WithId } from 'mongodb'; +import { InsertOneWriteOpResult, WithId } from 'mongodb'; import { BaseRaw } from './BaseRaw'; import { IEmailMessageHistory as T } from '../../../../definition/IEmailMessageHistory'; export class EmailMessageHistoryRaw extends BaseRaw { - protected indexes: IndexSpecification[] = [{ key: { createdAt: 1 }, expireAfterSeconds: 60 * 60 * 24 }]; + protected modelIndexes() { + return [{ key: { createdAt: 1 }, expireAfterSeconds: 60 * 60 * 24 }]; + } async create({ _id, email }: T): Promise>> { return this.insertOne({ diff --git a/app/models/server/raw/EmojiCustom.ts b/app/models/server/raw/EmojiCustom.ts index 17d45215a5ed..538be1ae3fef 100644 --- a/app/models/server/raw/EmojiCustom.ts +++ b/app/models/server/raw/EmojiCustom.ts @@ -1,10 +1,12 @@ import { Cursor, FindOneOptions, InsertOneWriteOpResult, UpdateWriteOpResult, WithId, WithoutProjection } from 'mongodb'; -import { BaseRaw, IndexSpecification } from './BaseRaw'; +import { BaseRaw } from './BaseRaw'; import { IEmojiCustom as T } from '../../../../definition/IEmojiCustom'; export class EmojiCustomRaw extends BaseRaw { - protected indexes: IndexSpecification[] = [{ key: { name: 1 } }, { key: { aliases: 1 } }, { key: { extension: 1 } }]; + protected modelIndexes() { + return [{ key: { name: 1 } }, { key: { aliases: 1 } }, { key: { extension: 1 } }]; + } // find findByNameOrAlias(emojiName: string, options: WithoutProjection>): Cursor { diff --git a/app/models/server/raw/ExportOperations.ts b/app/models/server/raw/ExportOperations.ts index b29f54be4a3b..a1e8e95dd7c2 100644 --- a/app/models/server/raw/ExportOperations.ts +++ b/app/models/server/raw/ExportOperations.ts @@ -1,12 +1,14 @@ import { Cursor, UpdateWriteOpResult } from 'mongodb'; -import { BaseRaw, IndexSpecification } from './BaseRaw'; +import { BaseRaw } from './BaseRaw'; import { IExportOperation } from '../../../../definition/IExportOperation'; type T = IExportOperation; export class ExportOperationsRaw extends BaseRaw { - protected indexes: IndexSpecification[] = [{ key: { userId: 1 } }, { key: { status: 1 } }]; + protected modelIndexes() { + return [{ key: { userId: 1 } }, { key: { status: 1 } }]; + } findOnePending(): Promise { const query = { diff --git a/app/models/server/raw/FederationServers.ts b/app/models/server/raw/FederationServers.ts index 5118aa8d2fcf..d660784d2441 100644 --- a/app/models/server/raw/FederationServers.ts +++ b/app/models/server/raw/FederationServers.ts @@ -2,10 +2,12 @@ import { UpdateWriteOpResult } from 'mongodb'; import { Users } from './index'; import { IFederationServer } from '../../../../definition/Federation'; -import { BaseRaw, IndexSpecification } from './BaseRaw'; +import { BaseRaw } from './BaseRaw'; export class FederationServersRaw extends BaseRaw { - protected indexes: IndexSpecification[] = [{ key: { domain: 1 } }]; + protected modelIndexes() { + return [{ key: { domain: 1 } }]; + } saveDomain(domain: string): Promise { return this.updateOne( diff --git a/app/models/server/raw/IntegrationHistory.ts b/app/models/server/raw/IntegrationHistory.ts index 2b630ba2450d..41e763e91a78 100644 --- a/app/models/server/raw/IntegrationHistory.ts +++ b/app/models/server/raw/IntegrationHistory.ts @@ -2,6 +2,10 @@ import { BaseRaw } from './BaseRaw'; import { IIntegrationHistory } from '../../../../definition/IIntegrationHistory'; export class IntegrationHistoryRaw extends BaseRaw { + protected modelIndexes() { + return [{ key: { 'integration._id': 1, 'integration._createdBy._id': 1 } }]; + } + removeByIntegrationId(integrationId: string): ReturnType['deleteMany']> { return this.deleteMany({ 'integration._id': integrationId }); } diff --git a/app/models/server/raw/Integrations.ts b/app/models/server/raw/Integrations.ts index c1c7de6b07bd..e7b4f1cc8df7 100644 --- a/app/models/server/raw/Integrations.ts +++ b/app/models/server/raw/Integrations.ts @@ -1,8 +1,10 @@ -import { BaseRaw, IndexSpecification } from './BaseRaw'; +import { BaseRaw } from './BaseRaw'; import { IIntegration } from '../../../../definition/IIntegration'; export class IntegrationsRaw extends BaseRaw { - protected indexes: IndexSpecification[] = [{ key: { type: 1 } }]; + protected modelIndexes() { + return [{ key: { type: 1 } }]; + } findOneByUrl(url: string): Promise { return this.findOne({ url }); diff --git a/app/models/server/raw/LivechatBusinessHours.ts b/app/models/server/raw/LivechatBusinessHours.ts index a748c307cc31..425716fee781 100644 --- a/app/models/server/raw/LivechatBusinessHours.ts +++ b/app/models/server/raw/LivechatBusinessHours.ts @@ -34,7 +34,7 @@ export class LivechatBusinessHoursRaw extends BaseRaw { active: true, workHours: { $elemMatch: { - $or: [{ 'start.cron.dayOfWeek': day, 'finish.cron.dayOfWeek': day }], + $or: [{ 'start.cron.dayOfWeek': day }, { 'finish.cron.dayOfWeek': day }], open: true, }, }, diff --git a/app/models/server/raw/LivechatTrigger.ts b/app/models/server/raw/LivechatTrigger.ts index e8311c5faef0..a582a06778d3 100644 --- a/app/models/server/raw/LivechatTrigger.ts +++ b/app/models/server/raw/LivechatTrigger.ts @@ -1,10 +1,12 @@ import { Cursor, UpdateWriteOpResult } from 'mongodb'; -import { BaseRaw, IndexSpecification } from './BaseRaw'; +import { BaseRaw } from './BaseRaw'; import { ILivechatTrigger } from '../../../../definition/ILivechatTrigger'; export class LivechatTriggerRaw extends BaseRaw { - protected indexes: IndexSpecification[] = [{ key: { enabled: 1 } }]; + protected modelIndexes() { + return [{ key: { enabled: 1 } }]; + } findEnabled(): Cursor { return this.find({ enabled: true }); diff --git a/app/models/server/raw/NotificationQueue.ts b/app/models/server/raw/NotificationQueue.ts index 23b820ab2393..c98f115e066b 100644 --- a/app/models/server/raw/NotificationQueue.ts +++ b/app/models/server/raw/NotificationQueue.ts @@ -1,16 +1,18 @@ import { UpdateWriteOpResult } from 'mongodb'; -import { BaseRaw, IndexSpecification } from './BaseRaw'; +import { BaseRaw } from './BaseRaw'; import { INotification } from '../../../../definition/INotification'; export class NotificationQueueRaw extends BaseRaw { - protected indexes: IndexSpecification[] = [ - { key: { uid: 1 } }, - { key: { ts: 1 }, expireAfterSeconds: 2 * 60 * 60 }, - { key: { schedule: 1 }, sparse: true }, - { key: { sending: 1 }, sparse: true }, - { key: { error: 1 }, sparse: true }, - ]; + protected modelIndexes() { + return [ + { key: { uid: 1 } }, + { key: { ts: 1 }, expireAfterSeconds: 2 * 60 * 60 }, + { key: { schedule: 1 }, sparse: true }, + { key: { sending: 1 }, sparse: true }, + { key: { error: 1 }, sparse: true }, + ]; + } unsetSendingById(_id: string): Promise { return this.updateOne( diff --git a/app/models/server/raw/Nps.ts b/app/models/server/raw/Nps.ts index daf123b14d1c..513103057228 100644 --- a/app/models/server/raw/Nps.ts +++ b/app/models/server/raw/Nps.ts @@ -1,14 +1,12 @@ -import { UpdateWriteOpResult, Collection } from 'mongodb'; +import { UpdateWriteOpResult } from 'mongodb'; import { BaseRaw } from './BaseRaw'; import { INps, NPSStatus } from '../../../../definition/INps'; type T = INps; export class NpsRaw extends BaseRaw { - constructor(public readonly col: Collection, trash?: Collection) { - super(col, trash); - - this.col.createIndexes([{ key: { status: 1, expireAt: 1 } }]); + modelIndexes() { + return [{ key: { status: 1, expireAt: 1 } }]; } // get expired surveys still in progress diff --git a/app/models/server/raw/NpsVote.ts b/app/models/server/raw/NpsVote.ts index 5aa9c56bda88..19a6842894c0 100644 --- a/app/models/server/raw/NpsVote.ts +++ b/app/models/server/raw/NpsVote.ts @@ -1,14 +1,12 @@ -import { ObjectId, Collection, Cursor, FindOneOptions, UpdateWriteOpResult, WithoutProjection } from 'mongodb'; +import { ObjectId, Cursor, FindOneOptions, UpdateWriteOpResult, WithoutProjection } from 'mongodb'; import { INpsVote, INpsVoteStatus } from '../../../../definition/INps'; import { BaseRaw } from './BaseRaw'; type T = INpsVote; export class NpsVoteRaw extends BaseRaw { - constructor(public readonly col: Collection, trash?: Collection) { - super(col, trash); - - this.col.createIndexes([{ key: { npsId: 1, status: 1, sentAt: 1 } }, { key: { npsId: 1, identifier: 1 } }]); + modelIndexes() { + return [{ key: { npsId: 1, status: 1, sentAt: 1 } }, { key: { npsId: 1, identifier: 1 } }]; } findNotSentByNpsId(npsId: string, options?: WithoutProjection>): Cursor { diff --git a/app/models/server/raw/OEmbedCache.ts b/app/models/server/raw/OEmbedCache.ts index e7a4ff2f301d..dc46ab0f2335 100644 --- a/app/models/server/raw/OEmbedCache.ts +++ b/app/models/server/raw/OEmbedCache.ts @@ -1,12 +1,14 @@ import { DeleteWriteOpResultObject } from 'mongodb'; -import { BaseRaw, IndexSpecification } from './BaseRaw'; +import { BaseRaw } from './BaseRaw'; import { IOEmbedCache } from '../../../../definition/IOEmbedCache'; type T = IOEmbedCache; export class OEmbedCacheRaw extends BaseRaw { - protected indexes: IndexSpecification[] = [{ key: { updatedAt: 1 } }]; + protected modelIndexes() { + return [{ key: { updatedAt: 1 } }]; + } async createWithIdAndData(_id: string, data: any): Promise { const record = { diff --git a/app/models/server/raw/PbxEvents.ts b/app/models/server/raw/PbxEvents.ts new file mode 100644 index 000000000000..56570bc0cf18 --- /dev/null +++ b/app/models/server/raw/PbxEvents.ts @@ -0,0 +1,60 @@ +import { Cursor } from 'mongodb'; + +import { BaseRaw } from './BaseRaw'; +import { IPbxEvent } from '../../../../definition/IPbxEvent'; + +export class PbxEventsRaw extends BaseRaw { + protected modelIndexes() { + return [{ key: { uniqueId: 1 }, unique: true }]; + } + + findByEvents(callUniqueId: string, events: string[]): Cursor { + return this.find( + { + $or: [ + { + callUniqueId, + }, + { + callUniqueIdFallback: callUniqueId, + }, + ], + event: { + $in: events, + }, + }, + { + sort: { + ts: 1, + }, + }, + ); + } + + findOneByEvent(callUniqueId: string, event: string): Promise { + return this.findOne({ + $or: [ + { + callUniqueId, + }, + { + callUniqueIdFallback: callUniqueId, + }, + ], + event, + }); + } + + findOneByUniqueId(callUniqueId: string): Promise { + return this.findOne({ + $or: [ + { + callUniqueId, + }, + { + callUniqueIdFallback: callUniqueId, + }, + ], + }); + } +} diff --git a/app/models/server/raw/Permissions.ts b/app/models/server/raw/Permissions.ts index 267b1b36cd86..4e6445fdc869 100644 --- a/app/models/server/raw/Permissions.ts +++ b/app/models/server/raw/Permissions.ts @@ -1,8 +1,9 @@ import { BaseRaw } from './BaseRaw'; import { IPermission } from '../../../../definition/IPermission'; +import type { IRole } from '../../../../definition/IRole'; export class PermissionsRaw extends BaseRaw { - async createOrUpdate(name: string, roles: string[]): Promise { + async createOrUpdate(name: string, roles: IRole['_id'][]): Promise { const exists = await this.findOne>( { _id: name, @@ -18,7 +19,7 @@ export class PermissionsRaw extends BaseRaw { return this.update({ _id: name }, { $set: { roles } }, { upsert: true }).then((result) => result.result._id); } - async create(id: string, roles: string[]): Promise { + async create(id: string, roles: IRole['_id'][]): Promise { const exists = await this.findOneById>(id, { fields: { _id: 1 } }); if (exists) { @@ -28,15 +29,15 @@ export class PermissionsRaw extends BaseRaw { return this.update({ _id: id }, { $set: { roles } }, { upsert: true }).then((result) => result.result._id); } - async addRole(permission: string, role: string): Promise { + async addRole(permission: string, role: IRole['_id']): Promise { await this.update({ _id: permission, roles: { $ne: role } }, { $addToSet: { roles: role } }); } - async setRoles(permission: string, roles: string[]): Promise { + async setRoles(permission: string, roles: IRole['_id'][]): Promise { await this.update({ _id: permission }, { $set: { roles } }); } - async removeRole(permission: string, role: string): Promise { + async removeRole(permission: string, role: IRole['_id']): Promise { await this.update({ _id: permission, roles: role }, { $pull: { roles: role } }); } } diff --git a/app/models/server/raw/ReadReceipts.ts b/app/models/server/raw/ReadReceipts.ts index 768cb7c53263..1be190713da5 100644 --- a/app/models/server/raw/ReadReceipts.ts +++ b/app/models/server/raw/ReadReceipts.ts @@ -1,10 +1,12 @@ import { Cursor } from 'mongodb'; -import { BaseRaw, IndexSpecification } from './BaseRaw'; +import { BaseRaw } from './BaseRaw'; import { ReadReceipt } from '../../../../definition/ReadReceipt'; export class ReadReceiptsRaw extends BaseRaw { - protected indexes: IndexSpecification[] = [{ key: { roomId: 1, userId: 1, messageId: 1 }, unique: true }, { key: { messageId: 1 } }]; + protected modelIndexes() { + return [{ key: { roomId: 1, userId: 1, messageId: 1 }, unique: true }, { key: { messageId: 1 } }]; + } findByMessageId(messageId: string): Cursor { return this.find({ messageId }); diff --git a/app/models/server/raw/Roles.ts b/app/models/server/raw/Roles.ts index f4ec829e3b8f..3491162d154a 100644 --- a/app/models/server/raw/Roles.ts +++ b/app/models/server/raw/Roles.ts @@ -10,6 +10,7 @@ import type { } from 'mongodb'; import { IRole, IUser } from '../../../../definition/IUser'; +import type { IRoom } from '../../../../definition/IRoom'; import { BaseRaw } from './BaseRaw'; import { SubscriptionsRaw } from './Subscriptions'; import { UsersRaw } from './Users'; @@ -32,58 +33,42 @@ export class RolesRaw extends BaseRaw { return options ? this.find(query, options) : this.find(query); } - createOrUpdate( - name: IRole['name'], - scope: 'Users' | 'Subscriptions' = 'Users', - description = '', - protectedRole = true, - mandatory2fa = false, - ): Promise { - const queryData = { - name, - scope, - description, - protected: protectedRole, - mandatory2fa, - }; - - return this.updateOne({ _id: name }, { $set: queryData }, { upsert: true }); - } + async addUserRoles(userId: IUser['_id'], roles: IRole['_id'][], scope?: IRoom['_id']): Promise { + if (process.env.NODE_ENV === 'development' && (scope === 'Users' || scope === 'Subscriptions')) { + throw new Error('Roles.addUserRoles method received a role scope instead of a scope value.'); + } - async addUserRoles(userId: IUser['_id'], roles: IRole['_id'][], scope?: string): Promise { if (!Array.isArray(roles)) { roles = [roles]; process.env.NODE_ENV === 'development' && console.warn('[WARN] RolesRaw.addUserRoles: roles should be an array'); } - for await (const name of roles) { - const role = await this.findOne({ name }, { scope: 1 } as FindOneOptions); + for await (const roleId of roles) { + const role = await this.findOneById>(roleId, { projection: { scope: 1 } }); if (!role) { - process.env.NODE_ENV === 'development' && console.warn(`[WARN] RolesRaw.addUserRoles: role: ${name} not found`); + process.env.NODE_ENV === 'development' && console.warn(`[WARN] RolesRaw.addUserRoles: role: ${roleId} not found`); continue; } switch (role.scope) { case 'Subscriptions': - await this.models.Subscriptions.addRolesByUserId(userId, [name], scope); + await this.models.Subscriptions.addRolesByUserId(userId, [role._id], scope); break; case 'Users': default: - await this.models.Users.addRolesByUserId(userId, [name]); + await this.models.Users.addRolesByUserId(userId, [role._id]); } } return true; } - async isUserInRoles(userId: IUser['_id'], roles: IRole['_id'][], scope?: string): Promise { - if (!Array.isArray(roles)) { - // TODO: remove this check - roles = [roles]; - process.env.NODE_ENV === 'development' && console.warn('[WARN] RolesRaw.isUserInRoles: roles should be an array'); + async isUserInRoles(userId: IUser['_id'], roles: IRole['_id'][], scope?: IRoom['_id']): Promise { + if (process.env.NODE_ENV === 'development' && (scope === 'Users' || scope === 'Subscriptions')) { + throw new Error('Roles.isUserInRoles method received a role scope instead of a scope value.'); } - for await (const roleName of roles) { - const role = await this.findOne({ name: roleName }, { scope: 1 } as FindOneOptions); + for await (const roleId of roles) { + const role = await this.findOneById>(roleId, { projection: { scope: 1 } }); if (!role) { continue; @@ -91,13 +76,13 @@ export class RolesRaw extends BaseRaw { switch (role.scope) { case 'Subscriptions': - if (await this.models.Subscriptions.isUserInRole(userId, roleName, scope)) { + if (await this.models.Subscriptions.isUserInRole(userId, roleId, scope)) { return true; } break; case 'Users': default: - if (await this.models.Users.isUserInRole(userId, roleName)) { + if (await this.models.Users.isUserInRole(userId, roleId)) { return true; } } @@ -105,14 +90,13 @@ export class RolesRaw extends BaseRaw { return false; } - async removeUserRoles(userId: IUser['_id'], roles: IRole['_id'][], scope?: string): Promise { - if (!Array.isArray(roles)) { - // TODO: remove this check - roles = [roles]; - process.env.NODE_ENV === 'development' && console.warn('[WARN] RolesRaw.removeUserRoles: roles should be an array'); + async removeUserRoles(userId: IUser['_id'], roles: IRole['_id'][], scope?: IRoom['_id']): Promise { + if (process.env.NODE_ENV === 'development' && (scope === 'Users' || scope === 'Subscriptions')) { + throw new Error('Roles.removeUserRoles method received a role scope instead of a scope value.'); } - for await (const roleName of roles) { - const role = await this.findOne({ name: roleName }, { scope: 1 } as FindOneOptions); + + for await (const roleId of roles) { + const role = await this.findOneById>(roleId, { projection: { scope: 1 } }); if (!role) { continue; @@ -120,11 +104,11 @@ export class RolesRaw extends BaseRaw { switch (role.scope) { case 'Subscriptions': - scope && (await this.models.Subscriptions.removeRolesByUserId(userId, [roleName], scope)); + scope && (await this.models.Subscriptions.removeRolesByUserId(userId, [roleId], scope)); break; case 'Users': default: - await this.models.Users.removeRolesByUserId(userId, [roleName]); + await this.models.Users.removeRolesByUserId(userId, [roleId]); } } return true; @@ -157,6 +141,34 @@ export class RolesRaw extends BaseRaw { return this.findOne(query, options); } + async findOneByName

(name: IRole['name'], options?: any): Promise { + const query: FilterQuery = { + name, + }; + + return this.findOne(query, options); + } + + findInIds

(ids: IRole['_id'][], options?: FindOneOptions): P extends Pick ? Cursor

: Cursor { + const query: FilterQuery = { + name: { + $in: ids, + }, + }; + + return this.find(query, options || {}) as P extends Pick ? Cursor

: Cursor; + } + + findAllExceptIds

(ids: IRole['_id'][], options?: FindOneOptions): P extends Pick ? Cursor

: Cursor { + const query: FilterQuery = { + _id: { + $nin: ids, + }, + }; + + return this.find(query, options || {}) as P extends Pick ? Cursor

: Cursor; + } + updateById( _id: IRole['_id'], name: IRole['name'], @@ -174,22 +186,30 @@ export class RolesRaw extends BaseRaw { return this.updateOne({ _id }, { $set: queryData }, { upsert: true }); } - findUsersInRole(name: IRole['name'], scope?: string): Promise>; + findUsersInRole(roleId: IRole['_id'], scope?: IRoom['_id']): Promise>; findUsersInRole( - name: IRole['name'], - scope: string | undefined, + roleId: IRole['_id'], + scope: IRoom['_id'] | undefined, options: WithoutProjection>, ): Promise>; findUsersInRole

( - name: IRole['name'], - scope: string | undefined, + roleId: IRole['_id'], + scope: IRoom['_id'] | undefined, options: FindOneOptions

, ): Promise>; - async findUsersInRole

(name: IRole['name'], scope: string | undefined, options?: any | undefined): Promise | Cursor

> { - const role = await this.findOne({ name }, { scope: 1 } as FindOneOptions); + async findUsersInRole

( + roleId: IRole['_id'], + scope: IRoom['_id'] | undefined, + options?: any | undefined, + ): Promise | Cursor

> { + if (process.env.NODE_ENV === 'development' && (scope === 'Users' || scope === 'Subscriptions')) { + throw new Error('Roles.findUsersInRole method received a role scope instead of a scope value.'); + } + + const role = await this.findOneById>(roleId, { projection: { scope: 1 } }); if (!role) { throw new Error('RolesRaw.findUsersInRole: role not found'); @@ -197,16 +217,16 @@ export class RolesRaw extends BaseRaw { switch (role.scope) { case 'Subscriptions': - return this.models.Subscriptions.findUsersInRoles([name], scope, options); + return this.models.Subscriptions.findUsersInRoles([role._id], scope, options); case 'Users': default: - return this.models.Users.findUsersInRoles([name], options); + return this.models.Users.findUsersInRoles([role._id], null, options); } } createWithRandomId( name: IRole['name'], - scope: 'Users' | 'Subscriptions' = 'Users', + scope: IRole['scope'] = 'Users', description = '', protectedRole = true, mandatory2fa = false, @@ -222,8 +242,12 @@ export class RolesRaw extends BaseRaw { return this.insertOne(role); } - async canAddUserToRole(uid: IUser['_id'], name: IRole['name'], scope?: string): Promise { - const role = await this.findOne({ name }, { fields: { scope: 1 } } as FindOneOptions); + async canAddUserToRole(uid: IUser['_id'], roleId: IRole['_id'], scope?: IRoom['_id']): Promise { + if (process.env.NODE_ENV === 'development' && (scope === 'Users' || scope === 'Subscriptions')) { + throw new Error('Roles.canAddUserToRole method received a role scope instead of a scope value.'); + } + + const role = await this.findOne({ _id: roleId }, { fields: { scope: 1 } } as FindOneOptions); if (!role) { return false; } diff --git a/app/models/server/raw/ServerEvents.ts b/app/models/server/raw/ServerEvents.ts index e40a5e0d5627..ef9731eb86b6 100644 --- a/app/models/server/raw/ServerEvents.ts +++ b/app/models/server/raw/ServerEvents.ts @@ -1,8 +1,10 @@ -import { BaseRaw, IndexSpecification } from './BaseRaw'; +import { BaseRaw } from './BaseRaw'; import { IServerEvent, IServerEventType } from '../../../../definition/IServerEvent'; export class ServerEventsRaw extends BaseRaw { - protected indexes: IndexSpecification[] = [{ key: { t: 1, ip: 1, ts: -1 } }, { key: { 't': 1, 'u.username': 1, 'ts': -1 } }]; + protected modelIndexes() { + return [{ key: { t: 1, ip: 1, ts: -1 } }, { key: { 't': 1, 'u.username': 1, 'ts': -1 } }]; + } async findLastFailedAttemptByIp(ip: string): Promise { return this.findOne( diff --git a/app/models/server/raw/Sessions.ts b/app/models/server/raw/Sessions.ts index f96685cd27f3..726e9407d3f7 100644 --- a/app/models/server/raw/Sessions.ts +++ b/app/models/server/raw/Sessions.ts @@ -3,13 +3,20 @@ import { BulkWriteOperation, BulkWriteOpResultObject, Collection, - IndexSpecification, UpdateWriteOpResult, FilterQuery, Cursor, } from 'mongodb'; -import type { ISession } from '../../../../definition/ISession'; +import type { + ISession, + UserSessionAggregation, + DeviceSessionAggregation, + OSSessionAggregation, + UserSessionAggregationResult, + DeviceSessionAggregationResult, + OSSessionAggregationResult, +} from '../../../../definition/ISession'; import { BaseRaw, ModelOptionalId } from './BaseRaw'; import type { IUser } from '../../../../definition/IUser'; @@ -22,7 +29,6 @@ type DestructuredDateWithType = { }; type DestructuredRange = { start: DestructuredDate; end: DestructuredDate }; type DateRange = { start: Date; end: Date }; -type FullReturn = { year: number; month: number; day: number; data: ISession[] }; const matchBasedOnDate = (start: DestructuredDate, end: DestructuredDate): FilterQuery => { if (start.year === end.year && start.month === end.month) { @@ -269,9 +275,12 @@ export const aggregates = { ); }, - async getUniqueUsersOfYesterday(collection: Collection, { year, month, day }: DestructuredDate): Promise { + async getUniqueUsersOfYesterday( + collection: Collection, + { year, month, day }: DestructuredDate, + ): Promise { return collection - .aggregate([ + .aggregate([ { $match: { year, @@ -341,9 +350,9 @@ export const aggregates = { async getUniqueUsersOfLastMonthOrWeek( collection: Collection, { year, month, day, type = 'month' }: DestructuredDateWithType, - ): Promise { + ): Promise { return collection - .aggregate( + .aggregate( [ { $match: { @@ -524,9 +533,9 @@ export const aggregates = { async getUniqueDevicesOfLastMonthOrWeek( collection: Collection, { year, month, day, type = 'month' }: DestructuredDateWithType, - ): Promise { + ): Promise { return collection - .aggregate( + .aggregate( [ { $match: { @@ -573,9 +582,12 @@ export const aggregates = { .toArray(); }, - getUniqueDevicesOfYesterday(collection: Collection, { year, month, day }: DestructuredDate): Promise { + getUniqueDevicesOfYesterday( + collection: Collection, + { year, month, day }: DestructuredDate, + ): Promise { return collection - .aggregate([ + .aggregate([ { $match: { year, @@ -624,9 +636,9 @@ export const aggregates = { getUniqueOSOfLastMonthOrWeek( collection: Collection, { year, month, day, type = 'month' }: DestructuredDateWithType, - ): Promise { + ): Promise { return collection - .aggregate( + .aggregate( [ { $match: { @@ -674,9 +686,9 @@ export const aggregates = { .toArray(); }, - getUniqueOSOfYesterday(collection: Collection, { year, month, day }: DestructuredDate): Promise { + getUniqueOSOfYesterday(collection: Collection, { year, month, day }: DestructuredDate): Promise { return collection - .aggregate([ + .aggregate([ { $match: { year, @@ -725,18 +737,6 @@ export const aggregates = { }; export class SessionsRaw extends BaseRaw { - protected indexes: IndexSpecification[] = [ - { key: { instanceId: 1, sessionId: 1, year: 1, month: 1, day: 1 } }, - { key: { instanceId: 1, sessionId: 1, userId: 1 } }, - { key: { instanceId: 1, sessionId: 1 } }, - { key: { sessionId: 1 } }, - { key: { userId: 1 } }, - { key: { year: 1, month: 1, day: 1, type: 1 } }, - { key: { type: 1 } }, - { key: { ip: 1, loginAt: 1 } }, - { key: { _computedAt: 1 }, expireAfterSeconds: 60 * 60 * 24 * 45 }, - ]; - private secondaryCollection: Collection; constructor(public readonly col: Collection, public readonly colSecondary: Collection, trash?: Collection) { @@ -745,6 +745,20 @@ export class SessionsRaw extends BaseRaw { this.secondaryCollection = colSecondary; } + protected modelIndexes() { + return [ + { key: { instanceId: 1, sessionId: 1, year: 1, month: 1, day: 1 } }, + { key: { instanceId: 1, sessionId: 1, userId: 1 } }, + { key: { instanceId: 1, sessionId: 1 } }, + { key: { sessionId: 1 } }, + { key: { userId: 1 } }, + { key: { year: 1, month: 1, day: 1, type: 1 } }, + { key: { type: 1 } }, + { key: { ip: 1, loginAt: 1 } }, + { key: { _computedAt: 1 }, expireAfterSeconds: 60 * 60 * 24 * 45 }, + ]; + } + async getActiveUsersBetweenDates({ start, end }: DestructuredRange): Promise { return this.col .aggregate([ @@ -998,7 +1012,7 @@ export class SessionsRaw extends BaseRaw { .toArray(); } - async getUniqueUsersOfYesterday(): Promise { + async getUniqueUsersOfYesterday(): Promise { const date = new Date(); date.setDate(date.getDate() - 1); @@ -1018,7 +1032,7 @@ export class SessionsRaw extends BaseRaw { }; } - async getUniqueUsersOfLastMonth(): Promise { + async getUniqueUsersOfLastMonth(): Promise { const date = new Date(); date.setDate(date.getDate() - 1); @@ -1038,7 +1052,7 @@ export class SessionsRaw extends BaseRaw { }; } - async getUniqueUsersOfLastWeek(): Promise { + async getUniqueUsersOfLastWeek(): Promise { const date = new Date(); date.setDate(date.getDate() - 1); @@ -1059,7 +1073,7 @@ export class SessionsRaw extends BaseRaw { }; } - async getUniqueDevicesOfYesterday(): Promise { + async getUniqueDevicesOfYesterday(): Promise { const date = new Date(); date.setDate(date.getDate() - 1); @@ -1079,7 +1093,7 @@ export class SessionsRaw extends BaseRaw { }; } - async getUniqueDevicesOfLastMonth(): Promise { + async getUniqueDevicesOfLastMonth(): Promise { const date = new Date(); date.setDate(date.getDate() - 1); @@ -1099,7 +1113,7 @@ export class SessionsRaw extends BaseRaw { }; } - async getUniqueDevicesOfLastWeek(): Promise { + async getUniqueDevicesOfLastWeek(): Promise { const date = new Date(); date.setDate(date.getDate() - 1); @@ -1120,7 +1134,7 @@ export class SessionsRaw extends BaseRaw { }; } - async getUniqueOSOfYesterday(): Promise { + async getUniqueOSOfYesterday(): Promise { const date = new Date(); date.setDate(date.getDate() - 1); @@ -1136,7 +1150,7 @@ export class SessionsRaw extends BaseRaw { }; } - async getUniqueOSOfLastMonth(): Promise { + async getUniqueOSOfLastMonth(): Promise { const date = new Date(); date.setDate(date.getDate() - 1); @@ -1156,7 +1170,7 @@ export class SessionsRaw extends BaseRaw { }; } - async getUniqueOSOfLastWeek(): Promise { + async getUniqueOSOfLastWeek(): Promise { const date = new Date(); date.setDate(date.getDate() - 1); diff --git a/app/models/server/raw/Statistics.ts b/app/models/server/raw/Statistics.ts index 424cb3175efc..d332a7b652f2 100644 --- a/app/models/server/raw/Statistics.ts +++ b/app/models/server/raw/Statistics.ts @@ -1,8 +1,10 @@ -import { BaseRaw, IndexSpecification } from './BaseRaw'; +import { BaseRaw } from './BaseRaw'; import { IStatistic } from '../../../../definition/IStatistic'; export class StatisticsRaw extends BaseRaw { - protected indexes: IndexSpecification[] = [{ key: { createdAt: -1 } }]; + protected modelIndexes() { + return [{ key: { createdAt: -1 } }]; + } async findLast(): Promise { const options = { diff --git a/app/models/server/raw/Subscriptions.ts b/app/models/server/raw/Subscriptions.ts index ec6851e611ec..fd4e755f6a8d 100644 --- a/app/models/server/raw/Subscriptions.ts +++ b/app/models/server/raw/Subscriptions.ts @@ -82,7 +82,7 @@ export class SubscriptionsRaw extends BaseRaw { return cursor.count(); } - async isUserInRole(uid: IUser['_id'], roleName: IRole['name'], rid?: IRoom['_id']): Promise { + async isUserInRole(uid: IUser['_id'], roleId: IRole['_id'], rid?: IRoom['_id']): Promise { if (rid == null) { return null; } @@ -90,7 +90,7 @@ export class SubscriptionsRaw extends BaseRaw { const query = { 'u._id': uid, rid, - 'roles': roleName, + 'roles': roleId, }; return this.findOne(query, { projection: { roles: 1 } }); @@ -116,7 +116,7 @@ export class SubscriptionsRaw extends BaseRaw { return this.update(query, update, options); } - removeRolesByUserId(uid: IUser['_id'], roles: IRole['name'][], rid: IRoom['_id']): Promise { + removeRolesByUserId(uid: IUser['_id'], roles: IRole['_id'][], rid: IRoom['_id']): Promise { const query = { 'u._id': uid, rid, @@ -131,22 +131,22 @@ export class SubscriptionsRaw extends BaseRaw { return this.updateOne(query, update); } - findUsersInRoles(name: IRole['name'][], rid: string | undefined): Promise>; + findUsersInRoles(roles: IRole['_id'][], rid: string | undefined): Promise>; findUsersInRoles( - name: IRole['name'][], + roles: IRole['_id'][], rid: string | undefined, options: WithoutProjection>, ): Promise>; findUsersInRoles

( - name: IRole['name'][], + roles: IRole['_id'][], rid: string | undefined, options: FindOneOptions

, ): Promise>; async findUsersInRoles

( - roles: IRole['name'][], + roles: IRole['_id'][], rid: IRoom['_id'] | undefined, options?: FindOneOptions

, ): Promise> { @@ -164,7 +164,7 @@ export class SubscriptionsRaw extends BaseRaw { : this.models.Users.find({ _id: { $in: users } } as FilterQuery, options); } - addRolesByUserId(uid: IUser['_id'], roles: IRole['name'][], rid?: IRoom['_id']): Promise { + addRolesByUserId(uid: IUser['_id'], roles: IRole['_id'][], rid?: IRoom['_id']): Promise { if (!Array.isArray(roles)) { roles = [roles]; process.env.NODE_ENV === 'development' && console.warn('[WARN] Subscriptions.addRolesByUserId: roles should be an array'); diff --git a/app/models/server/raw/Team.ts b/app/models/server/raw/Team.ts index 15beb82c9f9d..d4037ac8001c 100644 --- a/app/models/server/raw/Team.ts +++ b/app/models/server/raw/Team.ts @@ -1,25 +1,11 @@ -import { - Collection, - WithoutProjection, - FindOneOptions, - Cursor, - UpdateWriteOpResult, - DeleteWriteOpResultObject, - FilterQuery, -} from 'mongodb'; +import { WithoutProjection, FindOneOptions, Cursor, UpdateWriteOpResult, DeleteWriteOpResultObject, FilterQuery } from 'mongodb'; import { BaseRaw } from './BaseRaw'; import { ITeam, TEAM_TYPE } from '../../../../definition/ITeam'; export class TeamRaw extends BaseRaw { - constructor(public readonly col: Collection, trash?: Collection) { - super(col, trash); - - this.col.createIndex({ name: 1 }, { unique: true }); - - // this.col.createIndexes([ - // { key: { status: 1, expireAt: 1 } }, - // ]); + protected modelIndexes() { + return [{ key: { name: 1 }, unique: true }]; } findByNames(names: Array): Cursor; diff --git a/app/models/server/raw/TeamMember.ts b/app/models/server/raw/TeamMember.ts index c8f12416a08c..d6d4b07a8afc 100644 --- a/app/models/server/raw/TeamMember.ts +++ b/app/models/server/raw/TeamMember.ts @@ -1,5 +1,4 @@ import { - Collection, WithoutProjection, FindOneOptions, Cursor, @@ -11,17 +10,20 @@ import { import { BaseRaw } from './BaseRaw'; import { ITeamMember } from '../../../../definition/ITeam'; -import { IUser } from '../../../../definition/IUser'; +import type { IUser, IRole } from '../../../../definition/IUser'; type T = ITeamMember; export class TeamMemberRaw extends BaseRaw { - constructor(public readonly col: Collection, trash?: Collection) { - super(col, trash); - - this.col.createIndexes([{ key: { teamId: 1 } }]); - - // teamId => userId should be unique - this.col.createIndex({ teamId: 1, userId: 1 }, { unique: true }); + protected modelIndexes() { + return [ + { + key: { teamId: 1 }, + }, + { + key: { teamId: 1, userId: 1 }, + unique: true, + }, + ]; } findByUserId(userId: string): Cursor; @@ -81,15 +83,15 @@ export class TeamMemberRaw extends BaseRaw { return options ? this.col.find({ teamId: { $in: teamIds } }, options) : this.col.find({ teamId: { $in: teamIds } }, options); } - findByTeamIdAndRole(teamId: string, role: string): Cursor; + findByTeamIdAndRole(teamId: string, role: IRole['_id']): Cursor; - findByTeamIdAndRole(teamId: string, role: string, options: WithoutProjection>): Cursor; + findByTeamIdAndRole(teamId: string, role: IRole['_id'], options: WithoutProjection>): Cursor; - findByTeamIdAndRole

(teamId: string, role: string, options: FindOneOptions

): Cursor

; + findByTeamIdAndRole

(teamId: string, role: IRole['_id'], options: FindOneOptions

): Cursor

; findByTeamIdAndRole

( teamId: string, - role: string, + role: IRole['_id'], options?: undefined | WithoutProjection> | FindOneOptions

, ): Cursor

| Cursor { return options ? this.col.find({ teamId, roles: role }, options) : this.col.find({ teamId, roles: role }); @@ -136,7 +138,7 @@ export class TeamMemberRaw extends BaseRaw { }); } - updateRolesByTeamIdAndUserId(teamId: string, userId: string, roles: Array): Promise { + updateRolesByTeamIdAndUserId(teamId: string, userId: string, roles: Array): Promise { return this.updateOne( { teamId, @@ -150,7 +152,7 @@ export class TeamMemberRaw extends BaseRaw { ); } - removeRolesByTeamIdAndUserId(teamId: string, userId: string, roles: Array): Promise { + removeRolesByTeamIdAndUserId(teamId: string, userId: string, roles: Array): Promise { return this.updateOne( { teamId, diff --git a/app/models/server/raw/Uploads.ts b/app/models/server/raw/Uploads.ts index 4bd0572bddd8..c8b9c0f23860 100644 --- a/app/models/server/raw/Uploads.ts +++ b/app/models/server/raw/Uploads.ts @@ -13,7 +13,7 @@ import { WriteOpResult, } from 'mongodb'; -import { BaseRaw, IndexSpecification, InsertionModel } from './BaseRaw'; +import { BaseRaw, InsertionModel } from './BaseRaw'; import { IUpload as T } from '../../../../definition/IUpload'; const fillTypeGroup = (fileData: Partial): void => { @@ -25,7 +25,9 @@ const fillTypeGroup = (fileData: Partial): void => { }; export class UploadsRaw extends BaseRaw { - protected indexes: IndexSpecification[] = [{ key: { rid: 1 } }, { key: { uploadedAt: 1 } }, { key: { typeGroup: 1 } }]; + protected modelIndexes() { + return [{ key: { rid: 1 } }, { key: { uploadedAt: 1 } }, { key: { typeGroup: 1 } }]; + } findNotHiddenFilesOfRoom(roomId: string, searchText: string, fileType: string, limit: number): Cursor { const fileQuery = { diff --git a/app/models/server/raw/UserDataFiles.ts b/app/models/server/raw/UserDataFiles.ts index 948456d6f77c..b29cc7d704ee 100644 --- a/app/models/server/raw/UserDataFiles.ts +++ b/app/models/server/raw/UserDataFiles.ts @@ -1,10 +1,12 @@ import { FindOneOptions, InsertOneWriteOpResult, WithId, WithoutProjection } from 'mongodb'; -import { BaseRaw, IndexSpecification } from './BaseRaw'; +import { BaseRaw } from './BaseRaw'; import { IUserDataFile as T } from '../../../../definition/IUserDataFile'; export class UserDataFilesRaw extends BaseRaw { - protected indexes: IndexSpecification[] = [{ key: { userId: 1 } }]; + protected modelIndexes() { + return [{ key: { userId: 1 } }]; + } findLastFileByUser(userId: string, options: WithoutProjection> = {}): Promise { const query = { diff --git a/app/models/server/raw/Users.js b/app/models/server/raw/Users.js index 433a4d016efb..5418fe07b413 100644 --- a/app/models/server/raw/Users.js +++ b/app/models/server/raw/Users.js @@ -11,6 +11,10 @@ export class UsersRaw extends BaseRaw { }; } + /** + * @param {string} uid + * @param {IRole['_id'][]} roles list of role ids + */ addRolesByUserId(uid, roles) { if (!Array.isArray(roles)) { roles = [roles]; @@ -29,6 +33,11 @@ export class UsersRaw extends BaseRaw { return this.updateOne(query, update); } + /** + * @param {IRole['_id'][]} roles list of role ids + * @param {null} scope the value for the role scope (room id) - not used in the users collection + * @param {any} options + */ findUsersInRoles(roles, scope, options) { roles = [].concat(roles); @@ -54,6 +63,11 @@ export class UsersRaw extends BaseRaw { return this.findOne(query, options); } + /** + * @param {IRole['_id'][] | IRole['_id']} roles the list of role ids + * @param {any} query + * @param {any} options + */ findUsersInRolesWithQuery(roles, query, options) { roles = [].concat(roles); @@ -190,10 +204,10 @@ export class UsersRaw extends BaseRaw { return this.find(query, options); } - isUserInRole(userId, roleName) { + isUserInRole(userId, roleId) { const query = { _id: userId, - roles: roleName, + roles: roleId, }; return this.findOne(query, { projection: { roles: 1 } }); @@ -866,6 +880,10 @@ export class UsersRaw extends BaseRaw { ); } + /** + * @param {string} uid + * @param {IRole['_id']} roles the list of role ids to remove + */ removeRolesByUserId(uid, roles) { const query = { _id: uid, @@ -909,4 +927,80 @@ export class UsersRaw extends BaseRaw { return this.updateOne(query, update); } + + // Voip functions + findOneByAgentUsername(username, options) { + const query = { username, roles: 'livechat-agent' }; + + return this.findOne(query, options); + } + + findOneByExtension(extension, options) { + const query = { + extension, + }; + + return this.findOne(query, options); + } + + findByExtensions(extensions, options) { + const query = { + extension: { + $in: extensions, + }, + }; + + return this.find(query, options); + } + + getVoipExtensionByUserId(userId, options) { + const query = { + _id: userId, + extension: { $exists: true }, + }; + return this.findOne(query, options); + } + + setExtension(userId, extension) { + const query = { + _id: userId, + }; + + const update = { + $set: { + extension, + }, + }; + return this.update(query, update); + } + + unsetExtension(userId) { + const query = { + _id: userId, + }; + const update = { + $unset: { + extension: true, + }, + }; + return this.update(query, update); + } + + getAvailableAgentsIncludingExt(includeExt, text, options) { + const query = { + roles: { $in: ['livechat-agent', 'livechat-manager', 'livechat-monitor'] }, + $and: [ + { $or: [...(includeExt ? [{ extension: includeExt }] : []), { extension: { $exists: false } }] }, + ...(text && text.trim() + ? [ + { + $or: [{ username: escapeRegExp(text) }, { name: escapeRegExp(text) }], + }, + ] + : []), + ], + }; + + return this.find(query, options); + } } diff --git a/app/models/server/raw/VoipRooms.ts b/app/models/server/raw/VoipRooms.ts new file mode 100644 index 000000000000..a70c089e3947 --- /dev/null +++ b/app/models/server/raw/VoipRooms.ts @@ -0,0 +1,171 @@ +import { FilterQuery, WithoutProjection, FindOneOptions, WriteOpResult, Cursor } from 'mongodb'; + +import { BaseRaw } from './BaseRaw'; +import { IVoipRoom, IRoomClosingInfo } from '../../../../definition/IRoom'; +import { Logger } from '../../../../server/lib/logger/Logger'; + +export class VoipRoomsRaw extends BaseRaw { + logger = new Logger('VoipRoomsRaw'); + + async findOneOpenByVisitorToken(visitorToken: string, options: FindOneOptions = {}): Promise { + const query: FilterQuery = { + 't': 'v', + 'open': true, + 'v.token': visitorToken, + }; + return this.findOne(query, options); + } + + findOpenByAgentId(agentId: string): Cursor { + return this.find({ + 't': 'v', + 'open': true, + 'servedBy._id': agentId, + }); + } + + async findOneByAgentId(agentId: string): Promise { + return this.findOne({ + 't': 'v', + 'open': true, + 'servedBy._id': agentId, + }); + } + + async findOneVoipRoomById(id: string, options: WithoutProjection> = {}): Promise { + const query: FilterQuery = { + t: 'v', + _id: id, + }; + return this.findOne(query, options); + } + + async findOneOpenByRoomIdAndVisitorToken( + roomId: string, + visitorToken: string, + options: FindOneOptions = {}, + ): Promise { + const query: FilterQuery = { + 't': 'v', + '_id': roomId, + 'open': true, + 'v.token': visitorToken, + }; + return this.findOne(query, options); + } + + async findOneByVisitorToken(visitorToken: string, options: FindOneOptions = {}): Promise { + const query: FilterQuery = { + 't': 'v', + 'v.token': visitorToken, + }; + return this.findOne(query, options); + } + + async findOneByIdAndVisitorToken( + _id: IVoipRoom['_id'], + visitorToken: string, + options: FindOneOptions = {}, + ): Promise { + const query: FilterQuery = { + 't': 'v', + _id, + 'v.token': visitorToken, + }; + return this.findOne(query, options); + } + + closeByRoomId(roomId: IVoipRoom['_id'], closeInfo: IRoomClosingInfo): Promise { + const { closer, closedBy, closedAt, callDuration, serviceTimeDuration, ...extraData } = closeInfo; + + return this.update( + { + _id: roomId, + t: 'v', + }, + { + $set: { + closer, + closedBy, + closedAt, + callDuration, + 'metrics.serviceTimeDuration': serviceTimeDuration, + 'v.status': 'offline', + ...extraData, + }, + $unset: { + open: 1, + }, + }, + ); + } + + findRoomsWithCriteria({ + agents, + open, + createdAt, + closedAt, + tags, + queue, + visitorId, + options = {}, + }: { + agents?: string[]; + open?: boolean; + createdAt?: { start?: string; end?: string }; + closedAt?: { start?: string; end?: string }; + tags?: string[]; + queue?: string; + visitorId?: string; + options?: { + sort?: Record; + count?: number; + fields?: Record; + offset?: number; + }; + }): Cursor { + const query: FilterQuery = { + t: 'v', + }; + + if (agents) { + query.$or = [{ 'servedBy._id': { $in: agents } }, { 'servedBy.username': { $in: agents } }]; + } + if (open !== undefined) { + query.open = { $exists: open }; + } + if (visitorId && visitorId !== 'undefined') { + query['v._id'] = visitorId; + } + if (createdAt && Object.keys(createdAt).length) { + query.ts = {}; + if (createdAt.start) { + query.ts.$gte = new Date(createdAt.start); + } + if (createdAt.end) { + query.ts.$lte = new Date(createdAt.end); + } + } + if (closedAt && Object.keys(closedAt).length) { + query.closedAt = {}; + if (closedAt.start) { + query.closedAt.$gte = new Date(closedAt.start); + } + if (closedAt.end) { + query.closedAt.$lte = new Date(closedAt.end); + } + } + if (tags) { + query.tags = { $in: tags }; + } + if (queue) { + query.queue = queue; + } + + return this.find(query, { + sort: options.sort || { name: 1 }, + skip: options.offset, + limit: options.count, + }); + } +} diff --git a/app/models/server/raw/WebdavAccounts.ts b/app/models/server/raw/WebdavAccounts.ts index 9c3d0a8b0fdd..9bda5ee83e93 100644 --- a/app/models/server/raw/WebdavAccounts.ts +++ b/app/models/server/raw/WebdavAccounts.ts @@ -1,7 +1,7 @@ /** * Webdav Accounts model */ -import type { Collection, FindOneOptions, Cursor, DeleteWriteOpResultObject } from 'mongodb'; +import type { FindOneOptions, Cursor, DeleteWriteOpResultObject } from 'mongodb'; import { BaseRaw } from './BaseRaw'; import { IWebdavAccount } from '../../../../definition/IWebdavAccount'; @@ -9,10 +9,8 @@ import { IWebdavAccount } from '../../../../definition/IWebdavAccount'; type T = IWebdavAccount; export class WebdavAccountsRaw extends BaseRaw { - constructor(public readonly col: Collection, trash?: Collection) { - super(col, trash); - - this.col.createIndex({ userId: 1 }); + protected modelIndexes() { + return [{ key: { userId: 1 } }]; } findOneByIdAndUserId(_id: string, userId: string, options: FindOneOptions): Promise { diff --git a/app/models/server/raw/_Users.d.ts b/app/models/server/raw/_Users.d.ts index bb29c4dbf691..9da4691882e5 100644 --- a/app/models/server/raw/_Users.d.ts +++ b/app/models/server/raw/_Users.d.ts @@ -4,10 +4,10 @@ import { IRole, IUser } from '../../../../definition/IUser'; import { BaseRaw } from './BaseRaw'; export interface IUserRaw extends BaseRaw { - isUserInRole(uid: IUser['_id'], name: IRole['name']): Promise; - removeRolesByUserId(uid: IUser['_id'], roles: IRole['name'][]): Promise; - findUsersInRoles(roles: IRole['name'][]): Promise; - addRolesByUserId(uid: IUser['_id'], roles: IRole['name'][]): Promise; + isUserInRole(uid: IUser['_id'], roleId: IRole['_id']): Promise; + removeRolesByUserId(uid: IUser['_id'], roles: IRole['_id'][]): Promise; + findUsersInRoles(roles: IRole['_id'][]): Promise; + addRolesByUserId(uid: IUser['_id'], roles: IRole['_id'][]): Promise; isUserInRoleScope(uid: IUser['_id']): Promise; new (...args: any): IUser; } diff --git a/app/models/server/raw/index.ts b/app/models/server/raw/index.ts index 68fd5a3c4ada..ddbd1693e459 100644 --- a/app/models/server/raw/index.ts +++ b/app/models/server/raw/index.ts @@ -53,6 +53,7 @@ import { UsersSessionsRaw } from './UsersSessions'; import { UserDataFilesRaw } from './UserDataFiles'; import { UploadsRaw } from './Uploads'; import { WebdavAccountsRaw } from './WebdavAccounts'; +import { VoipRoomsRaw } from './VoipRooms'; import ImportDataModel from '../models/ImportData'; import LivechatAgentActivityModel from '../models/LivechatAgentActivity'; import LivechatBusinessHoursModel from '../models/LivechatBusinessHours'; @@ -69,6 +70,8 @@ import RoomsModel from '../models/Rooms'; import SettingsModel from '../models/Settings'; import SubscriptionsModel from '../models/Subscriptions'; import UsersModel from '../models/Users'; +import { PbxEventsRaw } from './PbxEvents'; +import { isRunningMs } from '../../../../server/lib/isRunningMs'; const trashCollection = trash.rawCollection(); @@ -145,6 +148,8 @@ export const UsersSessions = new UsersSessionsRaw(db.collection('usersSessions') export const UserDataFiles = new UserDataFilesRaw(db.collection(`${prefix}user_data_files`), trashCollection); export const Uploads = new UploadsRaw(db.collection(`${prefix}uploads`), trashCollection); export const WebdavAccounts = new WebdavAccountsRaw(db.collection(`${prefix}webdav_accounts`), trashCollection); +export const VoipRoom = new VoipRoomsRaw(db.collection(`${prefix}room`), trashCollection); +export const PbxEvent = new PbxEventsRaw(db.collection('pbx_events'), trashCollection); const map = { [Messages.col.collectionName]: MessagesModel, @@ -156,7 +161,7 @@ const map = { [Rooms.col.collectionName]: RoomsModel, }; -if (!process.env.DISABLE_DB_WATCH) { +if (!isRunningMs()) { const models = { Messages, Users, @@ -173,6 +178,7 @@ if (!process.env.DISABLE_DB_WATCH) { IntegrationHistory, Integrations, EmailInbox, + PbxEvent, }; initWatchers(models, api.broadcastLocal.bind(api), (model, fn) => { diff --git a/app/nextcloud/lib/common.js b/app/nextcloud/lib/common.js index 30baa4097c79..337cffee667c 100644 --- a/app/nextcloud/lib/common.js +++ b/app/nextcloud/lib/common.js @@ -37,7 +37,7 @@ const fillServerURL = _.debounce( Meteor.startup(function () { if (Meteor.isServer) { - settings.get('Accounts_OAuth_Nextcloud_URL', () => fillServerURL()); + settings.watch('Accounts_OAuth_Nextcloud_URL', () => fillServerURL()); } else { Tracker.autorun(function () { return fillServerURL(); diff --git a/app/notifications/server/lib/Notifications.ts b/app/notifications/server/lib/Notifications.ts index b21667ae561c..f1f6d46ab13f 100644 --- a/app/notifications/server/lib/Notifications.ts +++ b/app/notifications/server/lib/Notifications.ts @@ -12,15 +12,6 @@ import { } from '../../../models/server/raw'; import './Presence'; -// TODO: Replace this in favor of the api.broadcast -// StreamerCentral.on('broadcast', (name, eventName, args) => { -// api.broadcast('stream', [ -// name, -// eventName, -// args, -// ]); -// }); - export class Stream extends Streamer { registerPublication(name: string, fn: (eventName: string, options: boolean | { useCollection?: boolean; args?: any }) => void): void { Meteor.publish(name, function (eventName, options) { diff --git a/app/oembed/server/server.js b/app/oembed/server/server.js index de2163e8f391..84f25366a558 100644 --- a/app/oembed/server/server.js +++ b/app/oembed/server/server.js @@ -1,7 +1,6 @@ import URL from 'url'; import querystring from 'querystring'; -import { fetch } from 'meteor/fetch'; import { camelCase } from 'change-case'; import _ from 'underscore'; import iconv from 'iconv-lite'; @@ -16,7 +15,7 @@ import { settings } from '../../settings/server'; import { isURL } from '../../utils/lib/isURL'; import { SystemLogger } from '../../../server/lib/logger/system'; import { Info } from '../../utils/server'; -import { getUnsafeAgent } from '../../../server/lib/getUnsafeAgent'; +import { fetch } from '../../../server/lib/http/fetch'; const OEmbed = {}; @@ -101,18 +100,19 @@ const getUrlContent = async function (urlObj, redirectCount = 5) { const sizeLimit = 250000; - const response = await fetch(url, { - compress: true, - follow: redirectCount, - headers: { - 'User-Agent': `${settings.get('API_Embed_UserAgent')} Rocket.Chat/${Info.version}`, - 'Accept-Language': settings.get('Language') || 'en', + const response = await fetch( + url, + { + compress: true, + follow: redirectCount, + headers: { + 'User-Agent': `${settings.get('API_Embed_UserAgent')} Rocket.Chat/${Info.version}`, + 'Accept-Language': settings.get('Language') || 'en', + }, + size: sizeLimit, // max size of the response body, this was not working as expected so I'm also manually verifying that on the iterator }, - size: sizeLimit, // max size of the response body, this was not working as expected so I'm also manually verifying that on the iterator - ...(settings.get('Allow_Invalid_SelfSigned_Certs') && { - agent: getUnsafeAgent(parsedUrl.protocol), - }), - }); + settings.get('Allow_Invalid_SelfSigned_Certs'), + ); let totalSize = 0; const chunks = []; diff --git a/app/otr/client/index.js b/app/otr/client/index.js index a24ea4f03ff6..2f503d293511 100644 --- a/app/otr/client/index.js +++ b/app/otr/client/index.js @@ -2,3 +2,4 @@ import './stylesheets/otr.css'; import './rocketchat.otr.room'; import './rocketchat.otr'; import './tabBar'; +import './messageTypes'; diff --git a/app/otr/client/messageTypes.ts b/app/otr/client/messageTypes.ts new file mode 100644 index 000000000000..24fe9fe12ccf --- /dev/null +++ b/app/otr/client/messageTypes.ts @@ -0,0 +1,22 @@ +import { Meteor } from 'meteor/meteor'; + +import { MessageTypes } from '../../ui-utils/client'; +import { otrSystemMessages } from '../lib/constants'; + +Meteor.startup(() => { + MessageTypes.registerType({ + id: otrSystemMessages.USER_JOINED_OTR, + system: true, + message: otrSystemMessages.USER_JOINED_OTR, + }); + MessageTypes.registerType({ + id: otrSystemMessages.USER_REQUESTED_OTR_KEY_REFRESH, + system: true, + message: otrSystemMessages.USER_REQUESTED_OTR_KEY_REFRESH, + }); + MessageTypes.registerType({ + id: otrSystemMessages.USER_KEY_REFRESHED_SUCCESSFULLY, + system: true, + message: otrSystemMessages.USER_KEY_REFRESHED_SUCCESSFULLY, + }); +}); diff --git a/app/otr/client/rocketchat.otr.js b/app/otr/client/rocketchat.otr.js index 8606fad2fb26..c4eee72e92c6 100644 --- a/app/otr/client/rocketchat.otr.js +++ b/app/otr/client/rocketchat.otr.js @@ -74,6 +74,9 @@ Meteor.startup(function () { message.msg = t('Encrypted_message'); return Promise.resolve(message); } + if (message.t !== 'otr') { + return message; + } const otrRoom = OTR.getInstanceByRoomId(message.rid); return otrRoom.decrypt(message.msg).then((data) => { const { _id, text, ack } = data; diff --git a/app/otr/client/rocketchat.otr.room.js b/app/otr/client/rocketchat.otr.room.js index faaa6a60d3dc..1c943066813c 100644 --- a/app/otr/client/rocketchat.otr.room.js +++ b/app/otr/client/rocketchat.otr.room.js @@ -15,6 +15,8 @@ import { goToRoomById } from '../../../client/lib/utils/goToRoomById'; import { imperativeModal } from '../../../client/lib/imperativeModal'; import GenericModal from '../../../client/components/GenericModal'; import { dispatchToastMessage } from '../../../client/lib/toast'; +import { otrSystemMessages } from '../lib/constants'; +import { APIClient } from '../../utils/client'; OTR.Room = class { constructor(userId, roomId) { @@ -23,6 +25,7 @@ OTR.Room = class { this.peerId = getUidDirectMessage(roomId); this.established = new ReactiveVar(false); this.establishing = new ReactiveVar(false); + this.isFirstOTR = true; this.userOnlineComputation = null; @@ -42,9 +45,15 @@ OTR.Room = class { refresh, }); }); + if (refresh) { + Meteor.call('sendSystemMessages', this.roomId, Meteor.user(), otrSystemMessages.USER_REQUESTED_OTR_KEY_REFRESH); + this.isFirstOTR = false; + } } acknowledge() { + APIClient.v1.post('statistics.telemetry', { params: [{ eventName: 'otrStats', timestamp: Date.now(), rid: this.roomId }] }); + Notifications.notifyUser(this.peerId, 'otr', 'acknowledge', { roomId: this.roomId, userId: this.userId, @@ -61,6 +70,7 @@ OTR.Room = class { } end() { + this.isFirstOTR = true; this.reset(); Notifications.notifyUser(this.peerId, 'otr', 'end', { roomId: this.roomId, @@ -245,7 +255,6 @@ OTR.Room = class { switch (type) { case 'handshake': let timeout = null; - const establishConnection = () => { this.establishing.set(true); Meteor.clearTimeout(timeout); @@ -256,6 +265,9 @@ OTR.Room = class { Meteor.defer(() => { this.established.set(true); this.acknowledge(); + if (data.refresh) { + Meteor.call('sendSystemMessages', this.roomId, Meteor.user(), otrSystemMessages.USER_KEY_REFRESHED_SUCCESSFULLY); + } }); }); }); @@ -306,6 +318,10 @@ OTR.Room = class { this.importPublicKey(data.publicKey).then(() => { this.established.set(true); }); + if (this.isFirstOTR) { + Meteor.call('sendSystemMessages', this.roomId, Meteor.user(), otrSystemMessages.USER_JOINED_OTR); + } + this.isFirstOTR = false; break; case 'deny': diff --git a/app/otr/lib/constants.ts b/app/otr/lib/constants.ts new file mode 100644 index 000000000000..0911d9169f11 --- /dev/null +++ b/app/otr/lib/constants.ts @@ -0,0 +1,5 @@ +export enum otrSystemMessages { + USER_JOINED_OTR = 'user_joined_otr', + USER_REQUESTED_OTR_KEY_REFRESH = 'user_requested_otr_key_refresh', + USER_KEY_REFRESHED_SUCCESSFULLY = 'user_key_refreshed_successfully', +} diff --git a/app/otr/server/index.js b/app/otr/server/index.js index 1df0c14f3b15..8d489ec9c161 100644 --- a/app/otr/server/index.js +++ b/app/otr/server/index.js @@ -1,3 +1,4 @@ import './settings'; import './methods/deleteOldOTRMessages'; import './methods/updateOTRAck'; +import './methods/sendSystemMessages'; diff --git a/app/otr/server/methods/sendSystemMessages.ts b/app/otr/server/methods/sendSystemMessages.ts new file mode 100644 index 000000000000..e019021f5bc9 --- /dev/null +++ b/app/otr/server/methods/sendSystemMessages.ts @@ -0,0 +1,12 @@ +import { Meteor } from 'meteor/meteor'; + +import { Messages } from '../../../models/server'; + +Meteor.methods({ + sendSystemMessages(rid, user, id) { + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'sendSystemMessages' }); + } + Messages.createOtrSystemMessagesWithRoomIdAndUser(rid, user, id); + }, +}); diff --git a/app/otr/server/settings.ts b/app/otr/server/settings.ts index 9edf7e10e947..7490701d75d3 100644 --- a/app/otr/server/settings.ts +++ b/app/otr/server/settings.ts @@ -6,4 +6,8 @@ settingsRegistry.addGroup('OTR', function () { i18nLabel: 'Enabled', public: true, }); + this.add('OTR_Count', 0, { + type: 'int', + hidden: true, + }); }); diff --git a/app/reactions/client/init.js b/app/reactions/client/init.js index cf26fe42f5fc..fad585d9dcfa 100644 --- a/app/reactions/client/init.js +++ b/app/reactions/client/init.js @@ -1,12 +1,12 @@ import { Meteor } from 'meteor/meteor'; import { Blaze } from 'meteor/blaze'; -import { roomTypes } from '../../utils/client'; import { Rooms, Subscriptions } from '../../models'; import { MessageAction } from '../../ui-utils'; import { messageArgs } from '../../ui-utils/client/lib/messageArgs'; import { EmojiPicker } from '../../emoji'; import { tooltip } from '../../ui/client/components/tooltip'; +import { roomCoordinator } from '../../../client/lib/rooms/roomCoordinator'; export const EmojiEvents = { 'click .add-reaction'(event) { @@ -31,7 +31,7 @@ export const EmojiEvents = { return false; } - if (roomTypes.readOnly(room._id, user._id) && !room.reactWhenReadOnly) { + if (roomCoordinator.readOnly(room._id, user) && !room.reactWhenReadOnly) { return false; } @@ -87,10 +87,10 @@ Meteor.startup(function () { return false; } - if (roomTypes.readOnly(room._id, user._id) && !room.reactWhenReadOnly) { + if (roomCoordinator.readOnly(room._id, user) && !room.reactWhenReadOnly) { return false; } - const isLivechatRoom = roomTypes.isLivechatRoom(room.t); + const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t); if (isLivechatRoom) { return false; } diff --git a/app/reactions/client/methods/setReaction.js b/app/reactions/client/methods/setReaction.js index 8e98a7f2ae39..adc688f14bf0 100644 --- a/app/reactions/client/methods/setReaction.js +++ b/app/reactions/client/methods/setReaction.js @@ -4,7 +4,7 @@ import _ from 'underscore'; import { Messages, Rooms, Subscriptions } from '../../../models'; import { callbacks } from '../../../../lib/callbacks'; import { emoji } from '../../../emoji'; -import { roomTypes } from '../../../utils/client'; +import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator'; Meteor.methods({ setReaction(reaction, messageId, shouldReact, offlineTrigerred = false) { @@ -41,7 +41,7 @@ Meteor.methods({ return false; } - if (roomTypes.readOnly(room._id, user._id)) { + if (roomCoordinator.readOnly(room._id, user)) { return false; } diff --git a/app/search/server/events/events.js b/app/search/server/events/events.js index bb25afca8033..6bd5fe4bdffc 100644 --- a/app/search/server/events/events.js +++ b/app/search/server/events/events.js @@ -1,5 +1,3 @@ -import _ from 'underscore'; - import { settings } from '../../../settings/server'; import { callbacks } from '../../../../lib/callbacks'; import { searchProviderService } from '../service/providerService'; @@ -32,16 +30,12 @@ function afterDeleteMessage(m) { searchEventService.promoteEvent('message.delete', m._id); return m; } - -settings.get( - 'Search.Provider', - _.debounce(() => { - if (searchProviderService.activeProvider?.on) { - callbacks.add('afterSaveMessage', afterSaveMessage, callbacks.priority.MEDIUM, 'search-events'); - callbacks.add('afterDeleteMessage', afterDeleteMessage, callbacks.priority.MEDIUM, 'search-events-delete'); - } else { - callbacks.remove('afterSaveMessage', 'search-events'); - callbacks.remove('afterDeleteMessage', 'search-events-delete'); - } - }, 1000), -); +settings.watch('Search.Provider', () => { + if (searchProviderService.activeProvider?.on) { + callbacks.add('afterSaveMessage', afterSaveMessage, callbacks.priority.MEDIUM, 'search-events'); + callbacks.add('afterDeleteMessage', afterDeleteMessage, callbacks.priority.MEDIUM, 'search-events-delete'); + } else { + callbacks.remove('afterSaveMessage', 'search-events'); + callbacks.remove('afterDeleteMessage', 'search-events-delete'); + } +}); diff --git a/app/search/server/search.internalService.ts b/app/search/server/search.internalService.ts index 1e5efaa43569..03ed08742c04 100644 --- a/app/search/server/search.internalService.ts +++ b/app/search/server/search.internalService.ts @@ -1,11 +1,11 @@ import { Users } from '../../models/server'; import { settings } from '../../settings/server'; import { searchProviderService } from './service/providerService'; -import { ServiceClass } from '../../../server/sdk/types/ServiceClass'; +import { ServiceClassInternal } from '../../../server/sdk/types/ServiceClass'; import { api } from '../../../server/sdk/api'; import { searchEventService } from './events/events'; -class Search extends ServiceClass { +class Search extends ServiceClassInternal { protected name = 'search'; protected internal = true; diff --git a/app/slackbridge/server/SlackAdapter.js b/app/slackbridge/server/SlackAdapter.js index 2e0c192552d7..d17d5b64cbed 100644 --- a/app/slackbridge/server/SlackAdapter.js +++ b/app/slackbridge/server/SlackAdapter.js @@ -955,7 +955,7 @@ export default class SlackAdapter { imported: 'slackbridge', }); } else { - removeUserFromRoom(rocketChannel._id, rocketUser); + Promise.await(removeUserFromRoom(rocketChannel._id, rocketUser)); } } @@ -994,7 +994,7 @@ export default class SlackAdapter { imported: 'slackbridge', }); } else { - saveRoomName(rocketChannel._id, slackMessage.name, rocketUser, false); + Promise.await(saveRoomName(rocketChannel._id, slackMessage.name, rocketUser, false)); } } diff --git a/app/slackbridge/server/removeChannelLinks.js b/app/slackbridge/server/removeChannelLinks.js index 8fde9d9278cd..6ffc252806aa 100644 --- a/app/slackbridge/server/removeChannelLinks.js +++ b/app/slackbridge/server/removeChannelLinks.js @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { Rooms } from '../../models/server'; -import { hasRole } from '../../authorization/server'; +import { hasPermission } from '../../authorization/server'; import { settings } from '../../settings/server'; Meteor.methods({ @@ -13,7 +13,7 @@ Meteor.methods({ }); } - if (!hasRole(user._id, 'admin')) { + if (!hasPermission(user._id, 'remove-slackbridge-links')) { throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'removeSlackBridgeChannelLinks', }); diff --git a/app/slashcommands-archiveroom/client/client.js b/app/slashcommands-archiveroom/client/client.js deleted file mode 100644 index 31cd75a169f8..000000000000 --- a/app/slashcommands-archiveroom/client/client.js +++ /dev/null @@ -1,7 +0,0 @@ -import { slashCommands } from '../../utils'; - -slashCommands.add('archive', null, { - description: 'Archive', - params: '#channel', - permission: 'archive-room', -}); diff --git a/app/slashcommands-archiveroom/client/client.ts b/app/slashcommands-archiveroom/client/client.ts new file mode 100644 index 000000000000..bee69247b2f2 --- /dev/null +++ b/app/slashcommands-archiveroom/client/client.ts @@ -0,0 +1,15 @@ +import { slashCommands } from '../../utils/lib/slashCommand'; + +slashCommands.add( + 'archive', + undefined, + { + description: 'Archive', + params: '#channel', + permission: 'archive-room', + }, + undefined, + false, + undefined, + undefined, +); diff --git a/app/slashcommands-archiveroom/client/index.js b/app/slashcommands-archiveroom/client/index.ts similarity index 100% rename from app/slashcommands-archiveroom/client/index.js rename to app/slashcommands-archiveroom/client/index.ts diff --git a/app/slashcommands-archiveroom/server/index.js b/app/slashcommands-archiveroom/server/index.ts similarity index 100% rename from app/slashcommands-archiveroom/server/index.js rename to app/slashcommands-archiveroom/server/index.ts diff --git a/app/slashcommands-archiveroom/server/server.js b/app/slashcommands-archiveroom/server/server.js deleted file mode 100644 index 66c164bbf1c8..000000000000 --- a/app/slashcommands-archiveroom/server/server.js +++ /dev/null @@ -1,79 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { Match } from 'meteor/check'; -import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; - -import { Rooms, Messages } from '../../models'; -import { slashCommands } from '../../utils'; -import { api } from '../../../server/sdk/api'; - -function Archive(command, params, item) { - if (command !== 'archive' || !Match.test(params, String)) { - return; - } - - let channel = params.trim(); - let room; - - if (channel === '') { - room = Rooms.findOneById(item.rid); - channel = room.name; - } else { - channel = channel.replace('#', ''); - room = Rooms.findOneByName(channel); - } - - const user = Meteor.users.findOne(Meteor.userId()); - - if (!room) { - api.broadcast('notify.ephemeralMessage', Meteor.userId(), item.rid, { - msg: TAPi18n.__( - 'Channel_doesnt_exist', - { - postProcess: 'sprintf', - sprintf: [channel], - }, - user.language, - ), - }); - } - - // You can not archive direct messages. - if (room.t === 'd') { - return; - } - - if (room.archived) { - api.broadcast('notify.ephemeralMessage', Meteor.userId(), item.rid, { - msg: TAPi18n.__( - 'Duplicate_archived_channel_name', - { - postProcess: 'sprintf', - sprintf: [channel], - }, - user.language, - ), - }); - return; - } - Meteor.call('archiveRoom', room._id); - - Messages.createRoomArchivedByRoomIdAndUser(room._id, Meteor.user()); - api.broadcast('notify.ephemeralMessage', Meteor.userId(), item.rid, { - msg: TAPi18n.__( - 'Channel_Archived', - { - postProcess: 'sprintf', - sprintf: [channel], - }, - user.language, - ), - }); - - return Archive; -} - -slashCommands.add('archive', Archive, { - description: 'Archive', - params: '#channel', - permission: 'archive-room', -}); diff --git a/app/slashcommands-archiveroom/server/server.ts b/app/slashcommands-archiveroom/server/server.ts new file mode 100644 index 000000000000..d7349278c10c --- /dev/null +++ b/app/slashcommands-archiveroom/server/server.ts @@ -0,0 +1,80 @@ +import { Meteor } from 'meteor/meteor'; +import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; + +import { Rooms, Messages } from '../../models/server'; +import { slashCommands } from '../../utils/lib/slashCommand'; +import { api } from '../../../server/sdk/api'; +import { settings } from '../../settings/server'; + +function Archive(_command: 'archive', params: string, item: Record): void | Function { + let channel = params.trim(); + + let room; + + if (channel === '') { + room = Rooms.findOneById(item.rid); + channel = room.name; + } else { + channel = channel.replace('#', ''); + room = Rooms.findOneByName(channel); + } + + const userId = Meteor.userId(); + + if (!userId) { + return; + } + + if (!room) { + api.broadcast('notify.ephemeralMessage', userId, item.rid, { + msg: TAPi18n.__('Channel_doesnt_exist', { + postProcess: 'sprintf', + sprintf: [channel], + lng: settings.get('Language') || 'en', + }), + }); + return; + } + + // You can not archive direct messages. + if (room.t === 'd') { + return; + } + + if (room.archived) { + api.broadcast('notify.ephemeralMessage', userId, item.rid, { + msg: TAPi18n.__('Duplicate_archived_channel_name', { + postProcess: 'sprintf', + sprintf: [channel], + lng: settings.get('Language') || 'en', + }), + }); + return; + } + Meteor.call('archiveRoom', room._id); + + Messages.createRoomArchivedByRoomIdAndUser(room._id, Meteor.user()); + api.broadcast('notify.ephemeralMessage', userId, item.rid, { + msg: TAPi18n.__('Channel_Archived', { + postProcess: 'sprintf', + sprintf: [channel], + lng: settings.get('Language') || 'en', + }), + }); + + return Archive; +} + +slashCommands.add( + 'archive', + Archive, + { + description: 'Archive', + params: '#channel', + permission: 'archive-room', + }, + undefined, + false, + undefined, + undefined, +); diff --git a/app/slashcommands-create/client/client.js b/app/slashcommands-create/client/client.js deleted file mode 100644 index 68a96f6ae3a8..000000000000 --- a/app/slashcommands-create/client/client.js +++ /dev/null @@ -1,7 +0,0 @@ -import { slashCommands } from '../../utils'; - -slashCommands.add('create', null, { - description: 'Create_A_New_Channel', - params: '#channel', - permission: ['create-c', 'create-p'], -}); diff --git a/app/slashcommands-create/client/client.ts b/app/slashcommands-create/client/client.ts new file mode 100644 index 000000000000..f618d472f096 --- /dev/null +++ b/app/slashcommands-create/client/client.ts @@ -0,0 +1,15 @@ +import { slashCommands } from '../../utils/lib/slashCommand'; + +slashCommands.add( + 'create', + undefined, + { + description: 'Create_A_New_Channel', + params: '#channel', + permission: ['create-c', 'create-p'], + }, + undefined, + false, + undefined, + undefined, +); diff --git a/app/slashcommands-create/client/index.js b/app/slashcommands-create/client/index.ts similarity index 100% rename from app/slashcommands-create/client/index.js rename to app/slashcommands-create/client/index.ts diff --git a/app/slashcommands-create/server/index.js b/app/slashcommands-create/server/index.ts similarity index 100% rename from app/slashcommands-create/server/index.js rename to app/slashcommands-create/server/index.ts diff --git a/app/slashcommands-create/server/server.js b/app/slashcommands-create/server/server.js deleted file mode 100644 index 14b6b6d3867b..000000000000 --- a/app/slashcommands-create/server/server.js +++ /dev/null @@ -1,62 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { Match } from 'meteor/check'; -import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; - -import { settings } from '../../settings'; -import { Rooms } from '../../models'; -import { slashCommands } from '../../utils'; -import { api } from '../../../server/sdk/api'; - -function Create(command, params, item) { - function getParams(str) { - const regex = /(--(\w+))+/g; - const result = []; - let m; - while ((m = regex.exec(str)) !== null) { - if (m.index === regex.lastIndex) { - regex.lastIndex++; - } - result.push(m[2]); - } - return result; - } - - const regexp = new RegExp(settings.get('UTF8_Channel_Names_Validation')); - - if (command !== 'create' || !Match.test(params, String)) { - return; - } - let channel = regexp.exec(params.trim()); - channel = channel ? channel[0] : ''; - if (channel === '') { - return; - } - - const user = Meteor.users.findOne(Meteor.userId()); - const room = Rooms.findOneByName(channel); - if (room != null) { - api.broadcast('notify.ephemeralMessage', Meteor.userId(), item.rid, { - msg: TAPi18n.__( - 'Channel_already_exist', - { - postProcess: 'sprintf', - sprintf: [channel], - }, - user.language, - ), - }); - return; - } - - if (getParams(params).indexOf('private') > -1) { - return Meteor.call('createPrivateGroup', channel, []); - } - - Meteor.call('createChannel', channel, []); -} - -slashCommands.add('create', Create, { - description: 'Create_A_New_Channel', - params: '#channel', - permission: ['create-c', 'create-p'], -}); diff --git a/app/slashcommands-create/server/server.ts b/app/slashcommands-create/server/server.ts new file mode 100644 index 000000000000..ada7ff233883 --- /dev/null +++ b/app/slashcommands-create/server/server.ts @@ -0,0 +1,68 @@ +import { Meteor } from 'meteor/meteor'; +import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; + +import { settings } from '../../settings/server'; +import { Rooms } from '../../models/server'; +import { slashCommands } from '../../utils/lib/slashCommand'; +import { api } from '../../../server/sdk/api'; + +function Create(_command: 'create', params: string, item: Record): void { + function getParams(str: string): string[] { + const regex = /(--(\w+))+/g; + const result = []; + let m; + while ((m = regex.exec(str)) !== null) { + if (m.index === regex.lastIndex) { + regex.lastIndex++; + } + result.push(m[2]); + } + return result; + } + + const regexp = new RegExp(settings.get('UTF8_Channel_Names_Validation') as string); + + const channel = regexp.exec(params.trim()); + + if (!channel) { + return; + } + + const channelStr: string = channel ? channel[0] : ''; + if (channelStr === '') { + return; + } + const userId = Meteor.userId() as string; + + const room = Rooms.findOneByName(channelStr); + if (room != null) { + api.broadcast('notify.ephemeralMessage', userId, item.rid, { + msg: TAPi18n.__('Channel_already_exist', { + postProcess: 'sprintf', + sprintf: [channelStr], + lng: settings.get('Language') || 'en', + }), + }); + return; + } + + if (getParams(params).indexOf('private') > -1) { + return Meteor.call('createPrivateGroup', channelStr, []); + } + + Meteor.call('createChannel', channelStr, []); +} + +slashCommands.add( + 'create', + Create, + { + description: 'Create_A_New_Channel', + params: '#channel', + permission: ['create-c', 'create-p'], + }, + undefined, + false, + undefined, + undefined, +); diff --git a/app/slashcommands-invite/client/client.js b/app/slashcommands-invite/client/client.js deleted file mode 100644 index f18c8d21c344..000000000000 --- a/app/slashcommands-invite/client/client.js +++ /dev/null @@ -1,7 +0,0 @@ -import { slashCommands } from '../../utils'; - -slashCommands.add('invite', undefined, { - description: 'Invite_user_to_join_channel', - params: '@username', - permission: 'add-user-to-joined-room', -}); diff --git a/app/slashcommands-invite/client/client.ts b/app/slashcommands-invite/client/client.ts new file mode 100644 index 000000000000..4a494287d0ae --- /dev/null +++ b/app/slashcommands-invite/client/client.ts @@ -0,0 +1,15 @@ +import { slashCommands } from '../../utils/lib/slashCommand'; + +slashCommands.add( + 'invite', + undefined, + { + description: 'Invite_user_to_join_channel', + params: '@username', + permission: 'add-user-to-joined-room', + }, + undefined, + false, + undefined, + undefined, +); diff --git a/app/slashcommands-invite/client/index.js b/app/slashcommands-invite/client/index.ts similarity index 100% rename from app/slashcommands-invite/client/index.js rename to app/slashcommands-invite/client/index.ts diff --git a/app/slashcommands-invite/server/index.js b/app/slashcommands-invite/server/index.ts similarity index 100% rename from app/slashcommands-invite/server/index.js rename to app/slashcommands-invite/server/index.ts diff --git a/app/slashcommands-invite/server/server.js b/app/slashcommands-invite/server/server.js deleted file mode 100644 index eca11742a34f..000000000000 --- a/app/slashcommands-invite/server/server.js +++ /dev/null @@ -1,90 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { Match } from 'meteor/check'; -import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; - -import { slashCommands } from '../../utils'; -import { Subscriptions } from '../../models'; -import { api } from '../../../server/sdk/api'; - -/* - * Invite is a named function that will replace /invite commands - * @param {Object} message - The message object - */ - -function Invite(command, params, item) { - if (command !== 'invite' || !Match.test(params, String)) { - return; - } - - const usernames = params - .split(/[\s,]/) - .map((username) => username.replace(/(^@)|( @)/, '')) - .filter((a) => a !== ''); - if (usernames.length === 0) { - return; - } - let users = Meteor.users.find({ - username: { - $in: usernames, - }, - }); - const userId = Meteor.userId(); - const currentUser = Meteor.users.findOne(userId); - if (users.count() === 0) { - api.broadcast('notify.ephemeralMessage', userId, item.rid, { - msg: TAPi18n.__( - 'User_doesnt_exist', - { - postProcess: 'sprintf', - sprintf: [usernames.join(' @')], - }, - currentUser.language, - ), - }); - return; - } - users = users.fetch().filter(function (user) { - const subscription = Subscriptions.findOneByRoomIdAndUserId(item.rid, user._id, { - fields: { _id: 1 }, - }); - if (subscription == null) { - return true; - } - api.broadcast('notify.ephemeralMessage', userId, item.rid, { - msg: TAPi18n.__( - 'Username_is_already_in_here', - { - postProcess: 'sprintf', - sprintf: [user.username], - }, - currentUser.language, - ), - }); - return false; - }); - - users.forEach(function (user) { - try { - return Meteor.call('addUserToRoom', { - rid: item.rid, - username: user.username, - }); - } catch ({ error }) { - if (error === 'cant-invite-for-direct-room') { - api.broadcast('notify.ephemeralMessage', userId, item.rid, { - msg: TAPi18n.__('Cannot_invite_users_to_direct_rooms', null, currentUser.language), - }); - } else { - api.broadcast('notify.ephemeralMessage', userId, item.rid, { - msg: TAPi18n.__(error, null, currentUser.language), - }); - } - } - }); -} - -slashCommands.add('invite', Invite, { - description: 'Invite_user_to_join_channel', - params: '@username', - permission: 'add-user-to-joined-room', -}); diff --git a/app/slashcommands-invite/server/server.ts b/app/slashcommands-invite/server/server.ts new file mode 100644 index 000000000000..afe867553602 --- /dev/null +++ b/app/slashcommands-invite/server/server.ts @@ -0,0 +1,91 @@ +import { Meteor } from 'meteor/meteor'; +import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; + +import { settings } from '../../settings/server'; +import { slashCommands } from '../../utils/lib/slashCommand'; +import { Subscriptions } from '../../models/server'; +import { api } from '../../../server/sdk/api'; + +/* + * Invite is a named function that will replace /invite commands + * @param {Object} message - The message object + */ + +function Invite(_command: 'invite', params: string, item: Record): void { + const usernames = params + .split(/[\s,]/) + .map((username) => username.replace(/(^@)|( @)/, '')) + .filter((a) => a !== ''); + if (usernames.length === 0) { + return; + } + const users = Meteor.users.find({ + username: { + $in: usernames, + }, + }); + const userId = Meteor.userId() as string; + if (users.count() === 0) { + api.broadcast('notify.ephemeralMessage', userId, item.rid, { + msg: TAPi18n.__('User_doesnt_exist', { + postProcess: 'sprintf', + sprintf: [usernames.join(' @')], + lng: settings.get('Language') || 'en', + }), + }); + return; + } + const usersFiltered = users.fetch().filter(function (user) { + const subscription = Subscriptions.findOneByRoomIdAndUserId(item.rid, user._id, { + fields: { _id: 1 }, + }); + if (subscription == null) { + return true; + } + const usernameStr = user.username as string; + api.broadcast('notify.ephemeralMessage', userId, item.rid, { + msg: TAPi18n.__('Username_is_already_in_here', { + postProcess: 'sprintf', + sprintf: [usernameStr], + lng: settings.get('Language') || 'en', + }), + }); + return false; + }); + + usersFiltered.forEach(function (user) { + try { + return Meteor.call('addUserToRoom', { + rid: item.rid, + username: user.username, + }); + } catch ({ error }) { + if (typeof error !== 'string') { + return; + } + if (error === 'cant-invite-for-direct-room') { + api.broadcast('notify.ephemeralMessage', userId, item.rid, { + msg: TAPi18n.__('Cannot_invite_users_to_direct_rooms', { lng: settings.get('Language') || 'en' }), + }); + } else { + api.broadcast('notify.ephemeralMessage', userId, item.rid, { + msg: TAPi18n.__(error, { lng: settings.get('Language') || 'en' }), + }); + } + } + }); +} + +slashCommands.add( + 'invite', + Invite, + { + description: 'Invite_user_to_join_channel', + params: '@username', + permission: 'add-user-to-joined-room', + }, + undefined, + false, + undefined, + undefined, +); diff --git a/app/slashcommands-me/index.js b/app/slashcommands-me/index.ts similarity index 100% rename from app/slashcommands-me/index.js rename to app/slashcommands-me/index.ts diff --git a/app/slashcommands-me/server/index.js b/app/slashcommands-me/server/index.ts similarity index 100% rename from app/slashcommands-me/server/index.js rename to app/slashcommands-me/server/index.ts diff --git a/app/slashcommands-me/server/me.js b/app/slashcommands-me/server/me.ts similarity index 68% rename from app/slashcommands-me/server/me.js rename to app/slashcommands-me/server/me.ts index 1de3d5f7d0fc..ff536c475460 100644 --- a/app/slashcommands-me/server/me.js +++ b/app/slashcommands-me/server/me.ts @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import s from 'underscore.string'; -import { slashCommands } from '../../utils'; +import { slashCommands } from '../../utils/lib/slashCommand'; /* * Me is a named function that will replace /me commands @@ -9,11 +9,7 @@ import { slashCommands } from '../../utils'; */ slashCommands.add( 'me', - function Me(command, params, item) { - if (command !== 'me') { - return; - } - + function Me(_command: 'me', params: string, item: Record): void { if (s.trim(params)) { const msg = item; msg.msg = `_${params}_`; @@ -24,4 +20,8 @@ slashCommands.add( description: 'Displays_action_text', params: 'your_message', }, + undefined, + false, + undefined, + undefined, ); diff --git a/app/slashcommands-mute/index.js b/app/slashcommands-mute/index.ts similarity index 100% rename from app/slashcommands-mute/index.js rename to app/slashcommands-mute/index.ts diff --git a/app/slashcommands-mute/server/index.js b/app/slashcommands-mute/server/index.ts similarity index 100% rename from app/slashcommands-mute/server/index.js rename to app/slashcommands-mute/server/index.ts diff --git a/app/slashcommands-mute/server/mute.js b/app/slashcommands-mute/server/mute.js deleted file mode 100644 index 6858cc07a563..000000000000 --- a/app/slashcommands-mute/server/mute.js +++ /dev/null @@ -1,66 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { Match } from 'meteor/check'; -import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; - -import { slashCommands } from '../../utils'; -import { Users, Subscriptions } from '../../models'; -import { api } from '../../../server/sdk/api'; - -/* - * Mute is a named function that will replace /mute commands - */ - -slashCommands.add( - 'mute', - function Mute(command, params, item) { - if (command !== 'mute' || !Match.test(params, String)) { - return; - } - const username = params.trim().replace('@', ''); - if (username === '') { - return; - } - const userId = Meteor.userId(); - const user = Meteor.users.findOne(userId); - const mutedUser = Users.findOneByUsernameIgnoringCase(username); - if (mutedUser == null) { - api.broadcast('notify.ephemeralMessage', userId, item.rid, { - msg: TAPi18n.__( - 'Username_doesnt_exist', - { - postProcess: 'sprintf', - sprintf: [username], - }, - user.language, - ), - }); - return; - } - - const subscription = Subscriptions.findOneByRoomIdAndUserId(item.rid, mutedUser._id, { - fields: { _id: 1 }, - }); - if (!subscription) { - api.broadcast('notify.ephemeralMessage', userId, item.rid, { - msg: TAPi18n.__( - 'Username_is_not_in_this_room', - { - postProcess: 'sprintf', - sprintf: [username], - }, - user.language, - ), - }); - return; - } - Meteor.call('muteUserInRoom', { - rid: item.rid, - username, - }); - }, - { - description: 'Mute_someone_in_room', - params: '@username', - permission: 'mute-user', - }, -); diff --git a/app/slashcommands-mute/server/mute.ts b/app/slashcommands-mute/server/mute.ts new file mode 100644 index 000000000000..ee000c5667f3 --- /dev/null +++ b/app/slashcommands-mute/server/mute.ts @@ -0,0 +1,61 @@ +import { Meteor } from 'meteor/meteor'; +import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; + +import { slashCommands } from '../../utils/lib/slashCommand'; +import { settings } from '../../settings/server'; +import { Users, Subscriptions } from '../../models/server'; +import { api } from '../../../server/sdk/api'; + +/* + * Mute is a named function that will replace /mute commands + */ + +function Mute(_command: 'mute', params: string, item: Record): void { + const username = params.trim().replace('@', ''); + if (username === '') { + return; + } + + const userId = Meteor.userId() as string; + const mutedUser = Users.findOneByUsernameIgnoringCase(username); + if (mutedUser == null) { + api.broadcast('notify.ephemeralMessage', userId, item.rid, { + msg: TAPi18n.__('Username_doesnt_exist', { + postProcess: 'sprintf', + sprintf: [username], + lng: settings.get('Language') || 'en', + }), + }); + } + const subscription = Subscriptions.findOneByRoomIdAndUserId(item.rid, mutedUser._id, { + fields: { _id: 1 }, + }); + if (!subscription) { + api.broadcast('notify.ephemeralMessage', userId, item.rid, { + msg: TAPi18n.__('Username_is_not_in_this_room', { + postProcess: 'sprintf', + sprintf: [username], + lng: settings.get('Language') || 'en', + }), + }); + return; + } + Meteor.call('muteUserInRoom', { + rid: item.rid, + username, + }); +} + +slashCommands.add( + 'mute', + Mute, + { + description: 'Mute_someone_in_room', + params: '@username', + permission: 'mute-user', + }, + undefined, + false, + undefined, + undefined, +); diff --git a/app/slashcommands-mute/server/unmute.js b/app/slashcommands-mute/server/unmute.js deleted file mode 100644 index 4d9a51857324..000000000000 --- a/app/slashcommands-mute/server/unmute.js +++ /dev/null @@ -1,63 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { Match } from 'meteor/check'; -import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; - -import { slashCommands } from '../../utils'; -import { Users, Subscriptions } from '../../models'; -import { api } from '../../../server/sdk/api'; - -/* - * Unmute is a named function that will replace /unmute commands - */ - -slashCommands.add( - 'unmute', - function Unmute(command, params, item) { - if (command !== 'unmute' || !Match.test(params, String)) { - return; - } - const username = params.trim().replace('@', ''); - if (username === '') { - return; - } - const user = Meteor.users.findOne(Meteor.userId()); - const unmutedUser = Users.findOneByUsernameIgnoringCase(username); - if (unmutedUser == null) { - return api.broadcast('notify.ephemeralMessage', Meteor.userId(), item.rid, { - msg: TAPi18n.__( - 'Username_doesnt_exist', - { - postProcess: 'sprintf', - sprintf: [username], - }, - user.language, - ), - }); - } - - const subscription = Subscriptions.findOneByRoomIdAndUserId(item.rid, unmutedUser._id, { - fields: { _id: 1 }, - }); - if (!subscription) { - return api.broadcast('notify.ephemeralMessage', Meteor.userId(), item.rid, { - msg: TAPi18n.__( - 'Username_is_not_in_this_room', - { - postProcess: 'sprintf', - sprintf: [username], - }, - user.language, - ), - }); - } - Meteor.call('unmuteUserInRoom', { - rid: item.rid, - username, - }); - }, - { - description: 'Unmute_someone_in_room', - params: '@username', - permission: 'mute-user', - }, -); diff --git a/app/slashcommands-mute/server/unmute.ts b/app/slashcommands-mute/server/unmute.ts new file mode 100644 index 000000000000..bcc43f1b1f10 --- /dev/null +++ b/app/slashcommands-mute/server/unmute.ts @@ -0,0 +1,60 @@ +import { Meteor } from 'meteor/meteor'; +import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; + +import { slashCommands } from '../../utils/lib/slashCommand'; +import { Users, Subscriptions } from '../../models/server'; +import { settings } from '../../settings/server'; +import { api } from '../../../server/sdk/api'; + +/* + * Unmute is a named function that will replace /unmute commands + */ + +function Unmute(_command: 'unmute', params: string, item: Record): void | Promise { + const username = params.trim().replace('@', ''); + if (username === '') { + return; + } + const userId = Meteor.userId() as string; + const unmutedUser = Users.findOneByUsernameIgnoringCase(username); + if (unmutedUser == null) { + return api.broadcast('notify.ephemeralMessage', userId, item.rid, { + msg: TAPi18n.__('Username_doesnt_exist', { + postProcess: 'sprintf', + sprintf: [username], + lng: settings.get('Language') || 'en', + }), + }); + } + + const subscription = Subscriptions.findOneByRoomIdAndUserId(item.rid, unmutedUser._id, { + fields: { _id: 1 }, + }); + if (!subscription) { + return api.broadcast('notify.ephemeralMessage', userId, item.rid, { + msg: TAPi18n.__('Username_is_not_in_this_room', { + postProcess: 'sprintf', + sprintf: [username], + lng: settings.get('Language') || 'en', + }), + }); + } + Meteor.call('unmuteUserInRoom', { + rid: item.rid, + username, + }); +} + +slashCommands.add( + 'unmute', + Unmute, + { + description: 'Unmute_someone_in_room', + params: '@username', + permission: 'mute-user', + }, + undefined, + false, + undefined, + undefined, +); diff --git a/app/slashcommands-open/client/client.js b/app/slashcommands-open/client/client.js index 07163509f7f0..9d630bddc83a 100644 --- a/app/slashcommands-open/client/client.js +++ b/app/slashcommands-open/client/client.js @@ -2,7 +2,8 @@ import { Meteor } from 'meteor/meteor'; import { Match } from 'meteor/check'; import { FlowRouter } from 'meteor/kadira:flow-router'; -import { slashCommands, roomTypes } from '../../utils'; +import { slashCommands } from '../../utils'; +import { roomCoordinator } from '../../../client/lib/rooms/roomCoordinator'; import { ChatSubscription, Subscriptions } from '../../models'; function Open(command, params /* , item*/) { @@ -32,7 +33,7 @@ function Open(command, params /* , item*/) { const subscription = ChatSubscription.findOne(query); if (subscription) { - roomTypes.openRouteLink(subscription.t, subscription, FlowRouter.current().queryParams); + roomCoordinator.openRouteLink(subscription.t, subscription, FlowRouter.current().queryParams); } if (type && type.indexOf('d') === -1) { @@ -43,7 +44,7 @@ function Open(command, params /* , item*/) { return; } const subscription = Subscriptions.findOne(query); - roomTypes.openRouteLink(subscription.t, subscription, FlowRouter.current().queryParams); + roomCoordinator.openRouteLink(subscription.t, subscription, FlowRouter.current().queryParams); }); } diff --git a/app/slashcommands-unarchiveroom/client/client.js b/app/slashcommands-unarchiveroom/client/client.js deleted file mode 100644 index 2495cf81535e..000000000000 --- a/app/slashcommands-unarchiveroom/client/client.js +++ /dev/null @@ -1,7 +0,0 @@ -import { slashCommands } from '../../utils'; - -slashCommands.add('unarchive', null, { - description: 'Unarchive', - params: '#channel', - permission: 'unarchive-room', -}); diff --git a/app/slashcommands-unarchiveroom/client/client.ts b/app/slashcommands-unarchiveroom/client/client.ts new file mode 100644 index 000000000000..aab7690912a0 --- /dev/null +++ b/app/slashcommands-unarchiveroom/client/client.ts @@ -0,0 +1,15 @@ +import { slashCommands } from '../../utils/lib/slashCommand'; + +slashCommands.add( + 'unarchive', + undefined, + { + description: 'Unarchive', + params: '#channel', + permission: 'unarchive-room', + }, + undefined, + false, + undefined, + undefined, +); diff --git a/app/slashcommands-unarchiveroom/client/index.js b/app/slashcommands-unarchiveroom/client/index.ts similarity index 100% rename from app/slashcommands-unarchiveroom/client/index.js rename to app/slashcommands-unarchiveroom/client/index.ts diff --git a/app/slashcommands-unarchiveroom/server/index.js b/app/slashcommands-unarchiveroom/server/index.ts similarity index 100% rename from app/slashcommands-unarchiveroom/server/index.js rename to app/slashcommands-unarchiveroom/server/index.ts diff --git a/app/slashcommands-unarchiveroom/server/server.js b/app/slashcommands-unarchiveroom/server/server.js deleted file mode 100644 index de1f3cf920aa..000000000000 --- a/app/slashcommands-unarchiveroom/server/server.js +++ /dev/null @@ -1,81 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { Match } from 'meteor/check'; -import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; - -import { Rooms, Messages } from '../../models'; -import { slashCommands } from '../../utils'; -import { roomTypes, RoomMemberActions } from '../../utils/server'; -import { api } from '../../../server/sdk/api'; - -function Unarchive(command, params, item) { - if (command !== 'unarchive' || !Match.test(params, String)) { - return; - } - - let channel = params.trim(); - let room; - - if (channel === '') { - room = Rooms.findOneById(item.rid); - channel = room.name; - } else { - channel = channel.replace('#', ''); - room = Rooms.findOneByName(channel); - } - - const user = Meteor.users.findOne(Meteor.userId()); - - if (!room) { - return api.broadcast('notify.ephemeralMessage', Meteor.userId(), item.rid, { - msg: TAPi18n.__( - 'Channel_doesnt_exist', - { - postProcess: 'sprintf', - sprintf: [channel], - }, - user.language, - ), - }); - } - - // You can not archive direct messages. - if (!roomTypes.getConfig(room.t).allowMemberAction(room, RoomMemberActions.ARCHIVE)) { - return; - } - - if (!room.archived) { - api.broadcast('notify.ephemeralMessage', Meteor.userId(), item.rid, { - msg: TAPi18n.__( - 'Channel_already_Unarchived', - { - postProcess: 'sprintf', - sprintf: [channel], - }, - user.language, - ), - }); - return; - } - - Meteor.call('unarchiveRoom', room._id); - - Messages.createRoomUnarchivedByRoomIdAndUser(room._id, Meteor.user()); - api.broadcast('notify.ephemeralMessage', Meteor.userId(), item.rid, { - msg: TAPi18n.__( - 'Channel_Unarchived', - { - postProcess: 'sprintf', - sprintf: [channel], - }, - user.language, - ), - }); - - return Unarchive; -} - -slashCommands.add('unarchive', Unarchive, { - description: 'Unarchive', - params: '#channel', - permission: 'unarchive-room', -}); diff --git a/app/slashcommands-unarchiveroom/server/server.ts b/app/slashcommands-unarchiveroom/server/server.ts new file mode 100644 index 000000000000..7441f39f97a4 --- /dev/null +++ b/app/slashcommands-unarchiveroom/server/server.ts @@ -0,0 +1,77 @@ +import { Meteor } from 'meteor/meteor'; +import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; + +import { Rooms, Messages } from '../../models/server'; +import { slashCommands } from '../../utils/lib/slashCommand'; +import { RoomMemberActions } from '../../../definition/IRoomTypeConfig'; +import { settings } from '../../settings/server'; +import { api } from '../../../server/sdk/api'; +import { roomCoordinator } from '../../../server/lib/rooms/roomCoordinator'; + +function Unarchive(_command: 'unarchive', params: string, item: Record): void | Promise | Function { + let channel = params.trim(); + let room; + + if (channel === '') { + room = Rooms.findOneById(item.rid); + channel = room.name; + } else { + channel = channel.replace('#', ''); + room = Rooms.findOneByName(channel); + } + + const userId = Meteor.userId() as string; + + if (!room) { + return api.broadcast('notify.ephemeralMessage', userId, item.rid, { + msg: TAPi18n.__('Channel_doesnt_exist', { + postProcess: 'sprintf', + sprintf: [channel], + lng: settings.get('Language') || 'en', + }), + }); + } + + // You can not archive direct messages. + if (!roomCoordinator.getRoomDirectives(room.t)?.allowMemberAction(room, RoomMemberActions.ARCHIVE)) { + return; + } + + if (!room.archived) { + api.broadcast('notify.ephemeralMessage', userId, item.rid, { + msg: TAPi18n.__('Channel_already_Unarchived', { + postProcess: 'sprintf', + sprintf: [channel], + lng: settings.get('Language') || 'en', + }), + }); + return; + } + + Meteor.call('unarchiveRoom', room._id); + + Messages.createRoomUnarchivedByRoomIdAndUser(room._id, Meteor.user()); + api.broadcast('notify.ephemeralMessage', userId, item.rid, { + msg: TAPi18n.__('Channel_Unarchived', { + postProcess: 'sprintf', + sprintf: [channel], + lng: settings.get('Language') || 'en', + }), + }); + + return Unarchive; +} + +slashCommands.add( + 'unarchive', + Unarchive, + { + description: 'Unarchive', + params: '#channel', + permission: 'unarchive-room', + }, + undefined, + false, + undefined, + undefined, +); diff --git a/app/statistics/server/functions/otrStats.ts b/app/statistics/server/functions/otrStats.ts new file mode 100644 index 000000000000..96dfe63f195a --- /dev/null +++ b/app/statistics/server/functions/otrStats.ts @@ -0,0 +1,13 @@ +import { updateCounter } from './updateStatsCounter'; +import { Rooms } from '../../../models/server'; +import telemetryEvent from '../lib/telemetryEvents'; + +type otrDataType = { rid: string }; + +export function otrStats(data: otrDataType): void { + updateCounter({ settingsId: 'OTR_Count' }); + + Rooms.setOTRForDMByRoomID(data.rid); +} + +telemetryEvent.register('otrStats', otrStats); diff --git a/app/statistics/server/functions/slashCommandsStats.ts b/app/statistics/server/functions/slashCommandsStats.ts new file mode 100644 index 000000000000..6d973a64c6be --- /dev/null +++ b/app/statistics/server/functions/slashCommandsStats.ts @@ -0,0 +1,12 @@ +import telemetryEvent from '../lib/telemetryEvents'; +import { updateCounter } from './updateStatsCounter'; + +type slashCommandsDataType = { command: string }; + +export function slashCommandsStats(data: slashCommandsDataType): void { + if (data.command === 'jitsi') { + updateCounter({ settingsId: 'Jitsi_Start_SlashCommands_Count' }); + } +} + +telemetryEvent.register('slashCommandsStats', slashCommandsStats); diff --git a/app/statistics/server/functions/updateStatsCounter.ts b/app/statistics/server/functions/updateStatsCounter.ts new file mode 100644 index 000000000000..4641b040f819 --- /dev/null +++ b/app/statistics/server/functions/updateStatsCounter.ts @@ -0,0 +1,10 @@ +import { Settings } from '../../../models/server'; +import telemetryEvent from '../lib/telemetryEvents'; + +type updateCounterDataType = { settingsId: string }; + +export function updateCounter(data: updateCounterDataType): void { + Settings.incrementValueById(data.settingsId); +} + +telemetryEvent.register('updateCounter', updateCounter); diff --git a/app/statistics/server/index.js b/app/statistics/server/index.js index 29f45ead81b4..9b1b614bcfbe 100644 --- a/app/statistics/server/index.js +++ b/app/statistics/server/index.js @@ -4,3 +4,6 @@ import './startup/monitor'; export { statistics } from './lib/statistics'; export { getLastStatistics } from './functions/getLastStatistics'; export { getStatistics } from './functions/getStatistics'; +export { updateCounter } from './functions/updateStatsCounter'; +export { slashCommandsStats } from './functions/slashCommandsStats'; +export { otrStats } from './functions/otrStats'; diff --git a/app/statistics/server/lib/SAUMonitor.ts b/app/statistics/server/lib/SAUMonitor.ts index c9f581af933e..6324e0f7d9e7 100644 --- a/app/statistics/server/lib/SAUMonitor.ts +++ b/app/statistics/server/lib/SAUMonitor.ts @@ -7,7 +7,7 @@ import { UAParserMobile, UAParserDesktop } from './UAParserCustom'; import { Sessions, Users } from '../../../models/server/raw'; import { aggregates } from '../../../models/server/raw/Sessions'; import { Logger } from '../../../../server/lib/logger/Logger'; -import { getMostImportantRole } from './getMostImportantRole'; +import { getMostImportantRole } from '../../../../lib/roles/getMostImportantRole'; import { sauEvents } from '../../../../server/services/sauMonitor/events'; import { ISession, ISessionDevice } from '../../../../definition/ISession'; import { ISocketConnection } from '../../../../definition/ISocketConnection'; diff --git a/app/statistics/server/lib/statistics.js b/app/statistics/server/lib/statistics.js deleted file mode 100644 index faea6cf98958..000000000000 --- a/app/statistics/server/lib/statistics.js +++ /dev/null @@ -1,271 +0,0 @@ -import os from 'os'; - -import _ from 'underscore'; -import { Meteor } from 'meteor/meteor'; -import { InstanceStatus } from 'meteor/konecty:multiple-instances-status'; -import { MongoInternals } from 'meteor/mongo'; - -import { Settings, Users, Rooms, Subscriptions, Messages, LivechatVisitors } from '../../../models/server'; -import { settings } from '../../../settings/server'; -import { Info, getMongoInfo } from '../../../utils/server'; -import { getControl } from '../../../../server/lib/migrations'; -import { getStatistics as federationGetStatistics } from '../../../federation/server/functions/dashboard'; -import { - NotificationQueue, - Users as UsersRaw, - Rooms as RoomsRaw, - Statistics, - Sessions, - Integrations, - Uploads, -} from '../../../models/server/raw'; -import { readSecondaryPreferred } from '../../../../server/database/readSecondaryPreferred'; -import { getAppsStatistics } from './getAppsStatistics'; -import { getServicesStatistics } from './getServicesStatistics'; -import { getStatistics as getEnterpriseStatistics } from '../../../../ee/app/license/server'; -import { Team, Analytics } from '../../../../server/sdk'; -import { getSettingsStatistics } from '../../../../server/lib/statistics/getSettingsStatistics'; - -const wizardFields = ['Organization_Type', 'Industry', 'Size', 'Country', 'Language', 'Server_Type', 'Register_Server']; - -const getUserLanguages = async (totalUsers) => { - const result = await UsersRaw.getUserLanguages(); - - const languages = { - none: totalUsers, - }; - - result.forEach(({ _id, total }) => { - if (!_id) { - return; - } - languages[_id] = total; - languages.none -= total; - }); - - return languages; -}; - -const { db } = MongoInternals.defaultRemoteCollectionDriver().mongo; - -export const statistics = { - get: async () => { - const readPreference = readSecondaryPreferred(db); - - const statistics = {}; - - // Setup Wizard - statistics.wizard = {}; - wizardFields.forEach((field) => { - const record = Settings.findOne(field); - if (record) { - const wizardField = field.replace(/_/g, '').replace(field[0], field[0].toLowerCase()); - statistics.wizard[wizardField] = record.value; - } - }); - - // Version - statistics.uniqueId = settings.get('uniqueID'); - if (Settings.findOne('uniqueID')) { - statistics.installedAt = Settings.findOne('uniqueID').createdAt; - } - - if (Info) { - statistics.version = Info.version; - statistics.tag = Info.tag; - statistics.branch = Info.branch; - } - - // User statistics - statistics.totalUsers = Users.find().count(); - statistics.activeUsers = Users.getActiveLocalUserCount(); - statistics.activeGuests = Users.getActiveLocalGuestCount(); - statistics.nonActiveUsers = Users.find({ active: false }).count(); - statistics.appUsers = Users.find({ type: 'app' }).count(); - statistics.onlineUsers = Meteor.users.find({ status: 'online' }).count(); - statistics.awayUsers = Meteor.users.find({ status: 'away' }).count(); - statistics.busyUsers = Meteor.users.find({ status: 'busy' }).count(); - statistics.totalConnectedUsers = statistics.onlineUsers + statistics.awayUsers; - statistics.offlineUsers = statistics.totalUsers - statistics.onlineUsers - statistics.awayUsers - statistics.busyUsers; - statistics.userLanguages = await getUserLanguages(statistics.totalUsers); - - // Room statistics - statistics.totalRooms = Rooms.find().count(); - statistics.totalChannels = Rooms.findByType('c').count(); - statistics.totalPrivateGroups = Rooms.findByType('p').count(); - statistics.totalDirect = Rooms.findByType('d').count(); - statistics.totalLivechat = Rooms.findByType('l').count(); - statistics.totalDiscussions = Rooms.countDiscussions(); - statistics.totalThreads = Messages.countThreads(); - - // Teams statistics - statistics.teams = await Team.getStatistics(); - - // livechat visitors - statistics.totalLivechatVisitors = LivechatVisitors.find().count(); - - // livechat agents - statistics.totalLivechatAgents = Users.findAgents().count(); - - // livechat enabled - statistics.livechatEnabled = settings.get('Livechat_enabled'); - - // Count and types of omnichannel rooms - statistics.omnichannelSources = (await RoomsRaw.allRoomSourcesCount().toArray()).map(({ _id: { id, alias, type }, count }) => ({ - id, - alias, - type, - count, - })); - - // Message statistics - statistics.totalChannelMessages = _.reduce( - Rooms.findByType('c', { fields: { msgs: 1 } }).fetch(), - function _countChannelMessages(num, room) { - return num + room.msgs; - }, - 0, - ); - statistics.totalPrivateGroupMessages = _.reduce( - Rooms.findByType('p', { fields: { msgs: 1 } }).fetch(), - function _countPrivateGroupMessages(num, room) { - return num + room.msgs; - }, - 0, - ); - statistics.totalDirectMessages = _.reduce( - Rooms.findByType('d', { fields: { msgs: 1 } }).fetch(), - function _countDirectMessages(num, room) { - return num + room.msgs; - }, - 0, - ); - statistics.totalLivechatMessages = _.reduce( - Rooms.findByType('l', { fields: { msgs: 1 } }).fetch(), - function _countLivechatMessages(num, room) { - return num + room.msgs; - }, - 0, - ); - statistics.totalMessages = - statistics.totalChannelMessages + - statistics.totalPrivateGroupMessages + - statistics.totalDirectMessages + - statistics.totalLivechatMessages; - - // Federation statistics - const federationOverviewData = federationGetStatistics(); - - statistics.federatedServers = federationOverviewData.numberOfServers; - statistics.federatedUsers = federationOverviewData.numberOfFederatedUsers; - - statistics.lastLogin = Users.getLastLogin(); - statistics.lastMessageSentAt = Messages.getLastTimestamp(); - statistics.lastSeenSubscription = Subscriptions.getLastSeen(); - - statistics.os = { - type: os.type(), - platform: os.platform(), - arch: os.arch(), - release: os.release(), - uptime: os.uptime(), - loadavg: os.loadavg(), - totalmem: os.totalmem(), - freemem: os.freemem(), - cpus: os.cpus(), - }; - - statistics.process = { - nodeVersion: process.version, - pid: process.pid, - uptime: process.uptime(), - }; - - statistics.deploy = { - method: process.env.DEPLOY_METHOD || 'tar', - platform: process.env.DEPLOY_PLATFORM || 'selfinstall', - }; - - statistics.readReceiptsEnabled = settings.get('Message_Read_Receipt_Enabled'); - statistics.readReceiptsDetailed = settings.get('Message_Read_Receipt_Store_Users'); - - statistics.enterpriseReady = true; - - statistics.uploadsTotal = await Uploads.find().count(); - const [result] = await Uploads.col - .aggregate( - [ - { - $group: { _id: 'total', total: { $sum: '$size' } }, - }, - ], - { readPreference }, - ) - .toArray(); - statistics.uploadsTotalSize = result ? result.total : 0; - - statistics.migration = getControl(); - statistics.instanceCount = InstanceStatus.getCollection() - .find({ _updatedAt: { $gt: new Date(Date.now() - process.uptime() * 1000 - 2000) } }) - .count(); - - const { oplogEnabled, mongoVersion, mongoStorageEngine } = getMongoInfo(); - statistics.oplogEnabled = oplogEnabled; - statistics.mongoVersion = mongoVersion; - statistics.mongoStorageEngine = mongoStorageEngine; - - statistics.uniqueUsersOfYesterday = await Sessions.getUniqueUsersOfYesterday(); - statistics.uniqueUsersOfLastWeek = await Sessions.getUniqueUsersOfLastWeek(); - statistics.uniqueUsersOfLastMonth = await Sessions.getUniqueUsersOfLastMonth(); - statistics.uniqueDevicesOfYesterday = await Sessions.getUniqueDevicesOfYesterday(); - statistics.uniqueDevicesOfLastWeek = await Sessions.getUniqueDevicesOfLastWeek(); - statistics.uniqueDevicesOfLastMonth = await Sessions.getUniqueDevicesOfLastMonth(); - statistics.uniqueOSOfYesterday = await Sessions.getUniqueOSOfYesterday(); - statistics.uniqueOSOfLastWeek = await Sessions.getUniqueOSOfLastWeek(); - statistics.uniqueOSOfLastMonth = await Sessions.getUniqueOSOfLastMonth(); - - statistics.apps = getAppsStatistics(); - statistics.services = getServicesStatistics(); - - // If getSettingsStatistics() returns an error, save as empty object. - const settingsStatisticsObject = (await getSettingsStatistics()) || {}; - statistics.settings = settingsStatisticsObject; - - const integrations = await Integrations.find( - {}, - { - projection: { - _id: 0, - type: 1, - enabled: 1, - scriptEnabled: 1, - }, - readPreference, - }, - ).toArray(); - - statistics.integrations = { - totalIntegrations: integrations.length, - totalIncoming: integrations.filter((integration) => integration.type === 'webhook-incoming').length, - totalIncomingActive: integrations.filter((integration) => integration.enabled === true && integration.type === 'webhook-incoming') - .length, - totalOutgoing: integrations.filter((integration) => integration.type === 'webhook-outgoing').length, - totalOutgoingActive: integrations.filter((integration) => integration.enabled === true && integration.type === 'webhook-outgoing') - .length, - totalWithScriptEnabled: integrations.filter((integration) => integration.scriptEnabled === true).length, - }; - - statistics.pushQueue = await NotificationQueue.col.estimatedDocumentCount(); - - statistics.enterprise = getEnterpriseStatistics(); - await Analytics.resetSeatRequestCount(); - - return statistics; - }, - async save() { - const rcStatistics = await statistics.get(); - rcStatistics.createdAt = new Date(); - await Statistics.insertOne(rcStatistics); - return rcStatistics; - }, -}; diff --git a/app/statistics/server/lib/statistics.ts b/app/statistics/server/lib/statistics.ts new file mode 100644 index 000000000000..1ad5faf65b50 --- /dev/null +++ b/app/statistics/server/lib/statistics.ts @@ -0,0 +1,472 @@ +import os from 'os'; +import { log } from 'console'; + +import _ from 'underscore'; +import { Meteor } from 'meteor/meteor'; +import { MongoInternals } from 'meteor/mongo'; + +import { Settings, Users, Rooms, Subscriptions, Messages, LivechatVisitors } from '../../../models/server'; +import { settings } from '../../../settings/server'; +import { Info, getMongoInfo } from '../../../utils/server'; +import { getControl } from '../../../../server/lib/migrations'; +import { getStatistics as federationGetStatistics } from '../../../federation/server/functions/dashboard'; +import { + NotificationQueue, + Users as UsersRaw, + Rooms as RoomsRaw, + Statistics, + Sessions, + Integrations, + Uploads, + LivechatDepartment, + EmailInbox, + LivechatBusinessHours, + Messages as MessagesRaw, + InstanceStatus, +} from '../../../models/server/raw'; +import { readSecondaryPreferred } from '../../../../server/database/readSecondaryPreferred'; +import { getAppsStatistics } from './getAppsStatistics'; +import { getServicesStatistics } from './getServicesStatistics'; +import { getStatistics as getEnterpriseStatistics } from '../../../../ee/app/license/server'; +import { Analytics } from '../../../../server/sdk'; +import { getSettingsStatistics } from '../../../../server/lib/statistics/getSettingsStatistics'; +import { IRoom } from '../../../../definition/IRoom'; +import { IStats } from '../../../../definition/IStats'; + +const wizardFields = ['Organization_Type', 'Industry', 'Size', 'Country', 'Language', 'Server_Type', 'Register_Server']; + +const getUserLanguages = async (totalUsers: number): Promise<{ [key: string]: number }> => { + const result = await UsersRaw.getUserLanguages(); + + const languages: { [key: string]: number } = { + none: totalUsers, + }; + + result.forEach(({ _id, total }: { _id: string; total: number }) => { + if (!_id) { + return; + } + languages[_id] = total; + languages.none -= total; + }); + + return languages; +}; + +const { db } = MongoInternals.defaultRemoteCollectionDriver().mongo; + +export const statistics = { + get: async (): Promise => { + const readPreference = readSecondaryPreferred(db); + + const statistics = {} as IStats; + const statsPms = []; + + // Setup Wizard + statistics.wizard = {}; + wizardFields.forEach((field) => { + const record = Settings.findOne(field); + if (record) { + const wizardField = field.replace(/_/g, '').replace(field[0], field[0].toLowerCase()); + statistics.wizard[wizardField] = record.value; + } + }); + + // Version + statistics.uniqueId = settings.get('uniqueID'); + if (Settings.findOne('uniqueID')) { + statistics.installedAt = Settings.findOne('uniqueID').createdAt; + } + + if (Info) { + statistics.version = Info.version; + statistics.tag = Info.tag; + statistics.branch = Info.branch; + } + + // User statistics + statistics.totalUsers = Users.find().count(); + statistics.activeUsers = Users.getActiveLocalUserCount(); + statistics.activeGuests = Users.getActiveLocalGuestCount(); + statistics.nonActiveUsers = Users.find({ active: false }).count(); + statistics.appUsers = Users.find({ type: 'app' }).count(); + statistics.onlineUsers = Meteor.users.find({ status: 'online' }).count(); + statistics.awayUsers = Meteor.users.find({ status: 'away' }).count(); + statistics.busyUsers = Meteor.users.find({ status: 'busy' }).count(); + statistics.totalConnectedUsers = statistics.onlineUsers + statistics.awayUsers; + statistics.offlineUsers = statistics.totalUsers - statistics.onlineUsers - statistics.awayUsers - statistics.busyUsers; + statsPms.push( + getUserLanguages(statistics.totalUsers).then((total) => { + statistics.userLanguages = total; + }), + ); + + // Room statistics + statistics.totalRooms = Rooms.find().count(); + statistics.totalChannels = Rooms.findByType('c').count(); + statistics.totalPrivateGroups = Rooms.findByType('p').count(); + statistics.totalDirect = Rooms.findByType('d').count(); + statistics.totalLivechat = Rooms.findByType('l').count(); + statistics.totalDiscussions = Rooms.countDiscussions(); + statistics.totalThreads = Messages.countThreads(); + + // livechat visitors + statistics.totalLivechatVisitors = LivechatVisitors.find().count(); + + // livechat agents + statistics.totalLivechatAgents = Users.findAgents().count(); + + // livechat enabled + statistics.livechatEnabled = settings.get('Livechat_enabled'); + + // Count and types of omnichannel rooms + statsPms.push( + RoomsRaw.allRoomSourcesCount() + .toArray() + .then((roomSources) => { + statistics.omnichannelSources = roomSources.map(({ _id: { id, alias, type }, count }) => ({ + id, + alias, + type, + count, + })); + }), + ); + + // Number of departments + statsPms.push( + LivechatDepartment.col.count().then((count) => { + statistics.departments = count; + }), + ); + + // Type of routing algorithm used on omnichannel + statistics.routingAlgorithm = settings.get('Livechat_Routing_Method') || ''; + + // is on-hold active + statistics.onHoldEnabled = settings.get('Livechat_allow_manual_on_hold'); + + // Number of Email Inboxes + statsPms.push( + EmailInbox.col.count().then((count) => { + statistics.emailInboxes = count; + }), + ); + + statsPms.push( + LivechatBusinessHours.col.count().then((count) => { + statistics.BusinessHours = { + // Number of Business Hours + total: count, + // Business Hours strategy + strategy: settings.get('Livechat_enable_business_hours') || '', + }; + }), + ); + + // Type of routing algorithm used on omnichannel + statistics.routingAlgorithm = settings.get('Livechat_Routing_Method'); + + // is on-hold active + statistics.onHoldEnabled = settings.get('Livechat_allow_manual_on_hold'); + + // Last-Chatted Agent Preferred (enabled/disabled) + statistics.lastChattedAgentPreferred = settings.get('Livechat_last_chatted_agent_routing'); + + // Assign new conversations to the contact manager (enabled/disabled) + statistics.assignNewConversationsToContactManager = settings.get('Omnichannel_contact_manager_routing'); + + // How to handle Visitor Abandonment setting + statistics.visitorAbandonment = settings.get('Livechat_abandoned_rooms_action'); + + // Amount of chats placed on hold + statsPms.push( + MessagesRaw.col.distinct('rid', { t: 'omnichannel_placed_chat_on_hold' }).then((msgs) => { + statistics.chatsOnHold = msgs.length; + }), + ); + + // VoIP Enabled + statistics.voipEnabled = settings.get('VoIP_Enabled'); + + // Amount of VoIP Calls + statsPms.push( + RoomsRaw.col + .find({ t: 'v' }) + .count() + .then((count) => { + statistics.voipCalls = count; + }), + ); + + // Amount of VoIP Extensions connected + statsPms.push( + UsersRaw.col + .find({ extension: { $exists: true } }) + .count() + .then((count) => { + statistics.voipExtensions = count; + }), + ); + + // Amount of Calls that ended properly + statsPms.push( + MessagesRaw.col + .find({ t: 'voip-call-wrapup' }) + .count() + .then((count) => { + statistics.voipSuccessfulCalls = count; + }), + ); + + // Amount of Calls that ended with an error + statsPms.push( + MessagesRaw.col + .find({ t: 'voip-call-ended-unexpectedly' }) + .count() + .then((count) => { + statistics.voipErrorCalls = count; + }), + ); + // Amount of Calls that were put on hold + statsPms.push( + MessagesRaw.col.distinct('rid', { t: 'voip-call-on-hold' }).then((msgs) => { + statistics.voipOnHoldCalls = msgs.length; + }), + ); + + // Message statistics + statistics.totalChannelMessages = _.reduce( + Rooms.findByType('c', { fields: { msgs: 1 } }).fetch(), + function _countChannelMessages(num: number, room: IRoom) { + return num + room.msgs; + }, + 0, + ); + statistics.totalPrivateGroupMessages = _.reduce( + Rooms.findByType('p', { fields: { msgs: 1 } }).fetch(), + function _countPrivateGroupMessages(num: number, room: IRoom) { + return num + room.msgs; + }, + 0, + ); + statistics.totalDirectMessages = _.reduce( + Rooms.findByType('d', { fields: { msgs: 1 } }).fetch(), + function _countDirectMessages(num: number, room: IRoom) { + return num + room.msgs; + }, + 0, + ); + statistics.totalLivechatMessages = _.reduce( + Rooms.findByType('l', { fields: { msgs: 1 } }).fetch(), + function _countLivechatMessages(num: number, room: IRoom) { + return num + room.msgs; + }, + 0, + ); + statistics.totalMessages = + statistics.totalChannelMessages + + statistics.totalPrivateGroupMessages + + statistics.totalDirectMessages + + statistics.totalLivechatMessages; + + // Federation statistics + statsPms.push( + federationGetStatistics().then((federationOverviewData) => { + statistics.federatedServers = federationOverviewData.numberOfServers; + statistics.federatedUsers = federationOverviewData.numberOfFederatedUsers; + }), + ); + + statistics.lastLogin = Users.getLastLogin(); + statistics.lastMessageSentAt = Messages.getLastTimestamp(); + statistics.lastSeenSubscription = Subscriptions.getLastSeen(); + + statistics.os = { + type: os.type(), + platform: os.platform(), + arch: os.arch(), + release: os.release(), + uptime: os.uptime(), + loadavg: os.loadavg(), + totalmem: os.totalmem(), + freemem: os.freemem(), + cpus: os.cpus(), + }; + + statistics.process = { + nodeVersion: process.version, + pid: process.pid, + uptime: process.uptime(), + }; + + statistics.deploy = { + method: process.env.DEPLOY_METHOD || 'tar', + platform: process.env.DEPLOY_PLATFORM || 'selfinstall', + }; + + statistics.readReceiptsEnabled = settings.get('Message_Read_Receipt_Enabled'); + statistics.readReceiptsDetailed = settings.get('Message_Read_Receipt_Store_Users'); + + statistics.enterpriseReady = true; + statsPms.push( + Uploads.find() + .count() + .then((count) => { + statistics.uploadsTotal = count; + }), + ); + statsPms.push( + Uploads.col + .aggregate( + [ + { + $group: { _id: 'total', total: { $sum: '$size' } }, + }, + ], + { readPreference }, + ) + .toArray() + .then((agg) => { + const [result] = agg; + statistics.uploadsTotalSize = result ? (result as any).total : 0; + }), + ); + + statistics.migration = getControl(); + statsPms.push( + InstanceStatus.col + .find({ _updatedAt: { $gt: new Date(Date.now() - process.uptime() * 1000 - 2000) } }) + .count() + .then((count) => { + statistics.instanceCount = count; + }), + ); + + const { oplogEnabled, mongoVersion, mongoStorageEngine } = getMongoInfo(); + statistics.oplogEnabled = oplogEnabled; + statistics.mongoVersion = mongoVersion; + statistics.mongoStorageEngine = mongoStorageEngine; + + statsPms.push( + Sessions.getUniqueUsersOfYesterday().then((result) => { + statistics.uniqueUsersOfYesterday = result; + }), + ); + statsPms.push( + Sessions.getUniqueUsersOfLastWeek().then((result) => { + statistics.uniqueUsersOfLastWeek = result; + }), + ); + statsPms.push( + Sessions.getUniqueUsersOfLastMonth().then((result) => { + statistics.uniqueUsersOfLastMonth = result; + }), + ); + statsPms.push( + Sessions.getUniqueDevicesOfYesterday().then((result) => { + statistics.uniqueDevicesOfYesterday = result; + }), + ); + statsPms.push( + Sessions.getUniqueDevicesOfLastWeek().then((result) => { + statistics.uniqueDevicesOfLastWeek = result; + }), + ); + statsPms.push( + Sessions.getUniqueDevicesOfLastMonth().then((result) => { + statistics.uniqueDevicesOfLastMonth = result; + }), + ); + statsPms.push( + Sessions.getUniqueOSOfYesterday().then((result) => { + statistics.uniqueOSOfYesterday = result; + }), + ); + statsPms.push( + Sessions.getUniqueOSOfLastWeek().then((result) => { + statistics.uniqueOSOfLastWeek = result; + }), + ); + statsPms.push( + Sessions.getUniqueOSOfLastMonth().then((result) => { + statistics.uniqueOSOfLastMonth = result; + }), + ); + + statistics.apps = getAppsStatistics(); + statistics.services = getServicesStatistics(); + + // If getSettingsStatistics() returns an error, save as empty object. + statsPms.push( + getSettingsStatistics().then((res) => { + const settingsStatisticsObject = res || {}; + statistics.settings = settingsStatisticsObject; + }), + ); + + statsPms.push( + Integrations.find( + {}, + { + projection: { + _id: 0, + type: 1, + enabled: 1, + scriptEnabled: 1, + }, + readPreference, + }, + ) + .toArray() + .then((found) => { + const integrations = found; + + statistics.integrations = { + totalIntegrations: integrations.length, + totalIncoming: integrations.filter((integration) => integration.type === 'webhook-incoming').length, + totalIncomingActive: integrations.filter( + (integration) => integration.enabled === true && integration.type === 'webhook-incoming', + ).length, + totalOutgoing: integrations.filter((integration) => integration.type === 'webhook-outgoing').length, + totalOutgoingActive: integrations.filter( + (integration) => integration.enabled === true && integration.type === 'webhook-outgoing', + ).length, + totalWithScriptEnabled: integrations.filter((integration) => integration.scriptEnabled === true).length, + }; + }), + ); + + statsPms.push( + NotificationQueue.col.estimatedDocumentCount().then((count) => { + statistics.pushQueue = count; + }), + ); + + statsPms.push( + getEnterpriseStatistics().then((result) => { + statistics.enterprise = result; + }), + ); + + statsPms.push(Analytics.resetSeatRequestCount()); + + statistics.dashboardCount = settings.get('Engagement_Dashboard_Load_Count'); + statistics.messageAuditApply = settings.get('Message_Auditing_Apply_Count'); + statistics.messageAuditLoad = settings.get('Message_Auditing_Panel_Load_Count'); + statistics.joinJitsiButton = settings.get('Jitsi_Click_To_Join_Count'); + statistics.slashCommandsJitsi = settings.get('Jitsi_Start_SlashCommands_Count'); + statistics.totalOTRRooms = Rooms.findByCreatedOTR().count(); + statistics.totalOTR = settings.get('OTR_Count'); + + await Promise.all(statsPms).catch(log); + + return statistics; + }, + async save(): Promise { + const rcStatistics = await statistics.get(); + rcStatistics.createdAt = new Date(); + await Statistics.insertOne(rcStatistics); + return rcStatistics; + }, +}; diff --git a/app/statistics/server/lib/telemetryEvents.ts b/app/statistics/server/lib/telemetryEvents.ts new file mode 100644 index 000000000000..85a82d53d8f6 --- /dev/null +++ b/app/statistics/server/lib/telemetryEvents.ts @@ -0,0 +1,24 @@ +import { TelemetryMap, ITelemetryEvent, TelemetryEvents } from '../../../../server/sdk/types/ITelemetryEvent'; + +type TelemetryEventResponse = Promise | void; +type TelemetryEventFunction = (data: TelemetryMap[T]) => TelemetryEventResponse; + +class TelemetryEvent implements ITelemetryEvent { + private events = new Map any>(); + + register(name: T, fn: TelemetryEventFunction): void { + this.events.set(name, fn); + } + + call(eventName: T, data: TelemetryMap[T]): TelemetryEventResponse { + const fn = this.events.get(eventName) as TelemetryEventFunction; + if (!fn) { + throw new Error('event not found'); + } + + return fn(data); + } +} + +const telemetryEvent = new TelemetryEvent(); +export default telemetryEvent; diff --git a/app/theme/client/imports/components/message-box.css b/app/theme/client/imports/components/message-box.css index 649a1e0826e7..92c801d2be82 100644 --- a/app/theme/client/imports/components/message-box.css +++ b/app/theme/client/imports/components/message-box.css @@ -274,6 +274,10 @@ margin: 0 0.5rem; } + &__join-it-button { + margin: 0 0.5rem; + } + &__cannot-send { display: flex; justify-content: space-between; diff --git a/app/theme/client/imports/general/base_old.css b/app/theme/client/imports/general/base_old.css index 795091f4e075..129756b5cf11 100644 --- a/app/theme/client/imports/general/base_old.css +++ b/app/theme/client/imports/general/base_old.css @@ -1880,8 +1880,6 @@ & .user { display: inline-block; - margin-right: 5px; - font-family: inherit; font-size: 0.875rem; diff --git a/app/threads/client/messageAction/follow.js b/app/threads/client/messageAction/follow.js index 08633f33962e..c927a5c1f64a 100644 --- a/app/threads/client/messageAction/follow.js +++ b/app/threads/client/messageAction/follow.js @@ -6,9 +6,9 @@ import { Messages } from '../../../models/client'; import { settings } from '../../../settings/client'; import { MessageAction } from '../../../ui-utils/client'; import { messageArgs } from '../../../ui-utils/client/lib/messageArgs'; -import { roomTypes } from '../../../utils/client'; import { callWithErrorHandling } from '../../../../client/lib/utils/callWithErrorHandling'; import { dispatchToastMessage } from '../../../../client/lib/toast'; +import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator'; Meteor.startup(function () { Tracker.autorun(() => { @@ -36,7 +36,7 @@ Meteor.startup(function () { replies = parentMessage.replies || []; } } - const isLivechatRoom = roomTypes.isLivechatRoom(room.t); + const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t); if (isLivechatRoom) { return false; } diff --git a/app/threads/client/messageAction/replyInThread.js b/app/threads/client/messageAction/replyInThread.js index ea4feae17592..5b8ba54c40b9 100644 --- a/app/threads/client/messageAction/replyInThread.js +++ b/app/threads/client/messageAction/replyInThread.js @@ -5,7 +5,7 @@ import { FlowRouter } from 'meteor/kadira:flow-router'; import { settings } from '../../../settings/client'; import { MessageAction } from '../../../ui-utils/client'; import { messageArgs } from '../../../ui-utils/client/lib/messageArgs'; -import { roomTypes } from '../../../utils/client'; +import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator'; Meteor.startup(function () { Tracker.autorun(() => { @@ -26,7 +26,7 @@ Meteor.startup(function () { }); }, condition({ subscription, room }) { - const isLivechatRoom = roomTypes.isLivechatRoom(room.t); + const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t); if (isLivechatRoom) { return false; } diff --git a/app/threads/server/methods/followMessage.js b/app/threads/server/methods/followMessage.js index 67bc1c63e3e3..b591a68f3a1b 100644 --- a/app/threads/server/methods/followMessage.js +++ b/app/threads/server/methods/followMessage.js @@ -4,7 +4,7 @@ import { check } from 'meteor/check'; import { Messages } from '../../../models/server'; import { RateLimiter } from '../../../lib/server'; import { settings } from '../../../settings/server'; -import { canAccessRoom } from '../../../authorization/server'; +import { canAccessRoomId } from '../../../authorization/server'; import { follow } from '../functions'; Meteor.methods({ @@ -27,7 +27,7 @@ Meteor.methods({ }); } - if (!canAccessRoom({ _id: message.rid }, { _id: uid })) { + if (!canAccessRoomId(message.rid, uid)) { throw new Meteor.Error('error-not-allowed', 'not-allowed', { method: 'followMessage' }); } diff --git a/app/threads/server/methods/unfollowMessage.js b/app/threads/server/methods/unfollowMessage.js index c81d9be06af6..e0f9189b386c 100644 --- a/app/threads/server/methods/unfollowMessage.js +++ b/app/threads/server/methods/unfollowMessage.js @@ -4,7 +4,7 @@ import { check } from 'meteor/check'; import { Messages } from '../../../models/server'; import { RateLimiter } from '../../../lib/server'; import { settings } from '../../../settings/server'; -import { canAccessRoom } from '../../../authorization/server'; +import { canAccessRoomId } from '../../../authorization/server'; import { unfollow } from '../functions'; Meteor.methods({ @@ -27,7 +27,7 @@ Meteor.methods({ }); } - if (!canAccessRoom({ _id: message.rid }, { _id: uid })) { + if (!canAccessRoomId(message.rid, uid)) { throw new Meteor.Error('error-not-allowed', 'not-allowed', { method: 'unfollowMessage' }); } diff --git a/app/tokenpass/client/index.js b/app/tokenpass/client/index.js index ebf16d3e0375..e51844feeee8 100644 --- a/app/tokenpass/client/index.js +++ b/app/tokenpass/client/index.js @@ -1,6 +1,5 @@ import '../lib/common'; import './startup'; -import './roomType'; import './tokenChannelsList.html'; import './tokenChannelsList'; import './tokenpassChannelSettings.html'; diff --git a/app/tokenpass/client/roomType.js b/app/tokenpass/client/roomType.js deleted file mode 100644 index ba751ad6c83d..000000000000 --- a/app/tokenpass/client/roomType.js +++ /dev/null @@ -1,23 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import { roomTypes, RoomTypeConfig } from '../../utils'; - -class TokenPassRoomType extends RoomTypeConfig { - constructor() { - super({ - identifier: 'tokenpass', - order: 1, - }); - - this.customTemplate = 'tokenChannelsList'; - } - - condition() { - const user = Meteor.users.findOne(Meteor.userId(), { fields: { 'services.tokenpass': 1 } }); - const hasTokenpass = !!(user && user.services && user.services.tokenpass); - - return hasTokenpass; - } -} - -roomTypes.add(new TokenPassRoomType()); diff --git a/app/tokenpass/lib/common.js b/app/tokenpass/lib/common.js index 525aa88fcad5..0cd6868a3672 100644 --- a/app/tokenpass/lib/common.js +++ b/app/tokenpass/lib/common.js @@ -24,7 +24,7 @@ const Tokenpass = new CustomOAuth('tokenpass', config); if (Meteor.isServer) { Meteor.startup(function () { - settings.get('API_Tokenpass_URL', function (key, value) { + settings.watch('API_Tokenpass_URL', function (value) { config.serverURL = value; Tokenpass.configure(config); }); diff --git a/app/tokenpass/server/cronRemoveUsers.js b/app/tokenpass/server/cronRemoveUsers.js index 7218f0342469..2376fc782434 100644 --- a/app/tokenpass/server/cronRemoveUsers.js +++ b/app/tokenpass/server/cronRemoveUsers.js @@ -32,7 +32,7 @@ function removeUsersFromTokenChannels() { const valid = Tokenpass.validateAccess(rooms[roomId], balances); if (!valid) { - removeUserFromRoom(roomId, userInfo); + Promise.await(removeUserFromRoom(roomId, userInfo)); } }); } diff --git a/app/tokenpass/server/roomAccessValidator.internalService.ts b/app/tokenpass/server/roomAccessValidator.internalService.ts index 0fcd51425b81..a7dc27044ae1 100644 --- a/app/tokenpass/server/roomAccessValidator.internalService.ts +++ b/app/tokenpass/server/roomAccessValidator.internalService.ts @@ -1,16 +1,15 @@ -import { ServiceClass } from '../../../server/sdk/types/ServiceClass'; +import { ServiceClassInternal } from '../../../server/sdk/types/ServiceClass'; import { validators } from './roomAccessValidator.compatibility'; -import { api } from '../../../server/sdk/api'; import { IAuthorizationTokenpass } from '../../../server/sdk/types/IAuthorizationTokenpass'; import { IRoom } from '../../../definition/IRoom'; import { IUser } from '../../../definition/IUser'; -class AuthorizationTokenpass extends ServiceClass implements IAuthorizationTokenpass { +export class AuthorizationTokenpass extends ServiceClassInternal implements IAuthorizationTokenpass { protected name = 'authorization-tokenpass'; protected internal = true; - async canAccessRoom(room: Partial, user: Partial): Promise { + async canAccessRoom(room: Pick, user: Pick): Promise { for (const validator of validators) { if (validator(room, user)) { return true; @@ -20,5 +19,3 @@ class AuthorizationTokenpass extends ServiceClass implements IAuthorizationToken return false; } } - -api.registerService(new AuthorizationTokenpass()); diff --git a/app/ui-cached-collection/client/models/CachedCollection.js b/app/ui-cached-collection/client/models/CachedCollection.js index 42ac3cfac730..e0d9de196656 100644 --- a/app/ui-cached-collection/client/models/CachedCollection.js +++ b/app/ui-cached-collection/client/models/CachedCollection.js @@ -210,7 +210,9 @@ export class CachedCollection extends Emitter { } }); - this.collection._collection._docs._map = new Map(data.records.map((record) => [record._id, record])); + this.collection._collection._docs._map = new Map( + data.records.map((record) => [this.collection._collection._docs._idStringify(record._id), record]), + ); this.updatedAt = data.updatedAt || this.updatedAt; @@ -226,7 +228,6 @@ export class CachedCollection extends Emitter { this.log(`${data.length} records loaded from server`); data.forEach((record) => { callbacks.run(`cachedCollection-loadFromServer-${this.name}`, record, 'changed'); - this.collection.direct.upsert({ _id: record._id }, _.omit(record, '_id')); this.onSyncData('changed', record); diff --git a/app/ui-login/client/login/form.js b/app/ui-login/client/login/form.js index 67e23147aad9..04d314aed854 100644 --- a/app/ui-login/client/login/form.js +++ b/app/ui-login/client/login/form.js @@ -129,6 +129,9 @@ Template.loginForm.events({ } callbacks.run('onUserLogin'); Session.set('forceLogin', false); + if (formData.secretURL) { + FlowRouter.go('home'); + } }); }); } diff --git a/app/ui-login/client/login/services.html b/app/ui-login/client/login/services.html index cc013fc5162b..bd5650bb4978 100644 --- a/app/ui-login/client/login/services.html +++ b/app/ui-login/client/login/services.html @@ -14,5 +14,5 @@ {{/each}}

- {{/if}} {{> AppleOauthButton}} + {{/if}} diff --git a/app/ui-message/client/actionButtons/lib/applyButtonFilters.ts b/app/ui-message/client/actionButtons/lib/applyButtonFilters.ts index 25677ff98d21..2a7d4418111e 100644 --- a/app/ui-message/client/actionButtons/lib/applyButtonFilters.ts +++ b/app/ui-message/client/actionButtons/lib/applyButtonFilters.ts @@ -4,7 +4,7 @@ import { Meteor } from 'meteor/meteor'; import { IUIActionButton, RoomTypeFilter } from '@rocket.chat/apps-engine/definition/ui'; -import { hasAtLeastOnePermission, hasPermission, hasRole } from '../../../../authorization/client'; +import { hasAtLeastOnePermission, hasPermission, hasRole, hasAnyRole } from '../../../../authorization/client'; import { IRoom, isDirectMessageRoom, @@ -19,10 +19,12 @@ import { export const applyAuthFilter = (button: IUIActionButton, room?: IRoom): boolean => { const { hasAllPermissions, hasOnePermission, hasAllRoles, hasOneRole } = button.when || {}; + const userId = Meteor.userId(); + const hasAllPermissionsResult = hasAllPermissions ? hasPermission(hasAllPermissions) : true; const hasOnePermissionResult = hasOnePermission ? hasAtLeastOnePermission(hasOnePermission) : true; - const hasAllRolesResult = hasAllRoles ? hasAllRoles.every((role) => hasRole(Meteor.userId(), role, room?._id)) : true; - const hasOneRoleResult = hasOneRole ? hasRole(Meteor.userId(), hasOneRole, room?._id) : true; + const hasAllRolesResult = hasAllRoles ? !!userId && hasAllRoles.every((role) => hasRole(userId, role, room?._id)) : true; + const hasOneRoleResult = hasOneRole ? !!userId && hasAnyRole(userId, hasOneRole, room?._id) : true; return hasAllPermissionsResult && hasOnePermissionResult && hasAllRolesResult && hasOneRoleResult; }; diff --git a/app/ui-message/client/message.html b/app/ui-message/client/message.html index afb4c1da94b0..82ee2e8be80a 100644 --- a/app/ui-message/client/message.html +++ b/app/ui-message/client/message.html @@ -49,7 +49,9 @@ {{#if isBot}} {{_ "Bot"}} {{/if}} + {{#unless system}} + {{/unless}} {{#if showTranslated}} @@ -111,6 +113,11 @@ {{> messageLocation location=msg.location}} {{/if}}
+ {{#if system}} +
+ +
+ {{/if}} {{#if hasOembed}} diff --git a/app/ui-message/client/message.js b/app/ui-message/client/message.js index 2d568a6e0a78..5a42d74251d4 100644 --- a/app/ui-message/client/message.js +++ b/app/ui-message/client/message.js @@ -12,7 +12,7 @@ import { normalizeThreadTitle } from '../../threads/client/lib/normalizeThreadTi import { MessageTypes, MessageAction } from '../../ui-utils/client'; import { RoomRoles, UserRoles, Roles } from '../../models/client'; import { Markdown } from '../../markdown/client'; -import { t, roomTypes } from '../../utils'; +import { t } from '../../utils'; import { AutoTranslate } from '../../autotranslate/client'; import { renderMentions } from '../../mentions/client/client'; import { renderMessageBody } from '../../../client/lib/utils/renderMessageBody'; @@ -21,6 +21,7 @@ import { formatTime } from '../../../client/lib/utils/formatTime'; import { formatDate } from '../../../client/lib/utils/formatDate'; import './messageThread'; import './message.html'; +import { roomCoordinator } from '../../../client/lib/rooms/roomCoordinator'; const renderBody = (msg, settings) => { const searchedText = msg.searchedText ? msg.searchedText : ''; @@ -373,7 +374,7 @@ Template.message.helpers({ return true; } - if (roomTypes.readOnly(room._id, u._id) && !room.reactWhenReadOnly) { + if (roomCoordinator.readOnly(room._id, u) && !room.reactWhenReadOnly) { return true; } }, @@ -422,15 +423,15 @@ Template.message.helpers({ if (room && room.t === 'd') { return 'at'; } - return roomTypes.getIcon(room); + return roomCoordinator.getIcon(room); }, customClass() { - const { customClass, msg } = this; - return customClass || msg.customClass; + const { customClass } = this; + return customClass; }, fromSearch() { - const { customClass, msg } = this; - return [msg.customClass, customClass].includes('search'); + const { customClass } = this; + return customClass === 'search'; }, actionContext() { const { msg } = this; diff --git a/app/ui-message/client/messageBox/messageBox.js b/app/ui-message/client/messageBox/messageBox.js index 4693c4f6dd08..87f72364de46 100644 --- a/app/ui-message/client/messageBox/messageBox.js +++ b/app/ui-message/client/messageBox/messageBox.js @@ -13,7 +13,7 @@ import { Users } from '../../../models'; import { settings } from '../../../settings'; import { fileUpload, KonchatNotification } from '../../../ui'; import { messageBox, popover } from '../../../ui-utils'; -import { t, roomTypes, getUserPreference } from '../../../utils/client'; +import { t, getUserPreference } from '../../../utils/client'; import './messageBoxActions'; import './messageBoxReplyPreview'; import './userActionIndicator.ts'; @@ -25,6 +25,7 @@ import { getImageExtensionFromMime } from '../../../../lib/getImageExtensionFrom import { keyCodes } from '../../../../client/lib/utils/keyCodes'; import { isRTL } from '../../../../client/lib/utils/isRTL'; import { call } from '../../../../client/lib/utils/call'; +import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator'; Template.messageBox.onCreated(function () { this.state = new ReactiveDict(); @@ -209,8 +210,8 @@ Template.messageBox.helpers({ return false; } - const isReadOnly = roomTypes.readOnly(rid, Users.findOne({ _id: Meteor.userId() }, { fields: { username: 1 } })); - const isArchived = roomTypes.archived(rid) || (subscription && subscription.t === 'd' && subscription.archived); + const isReadOnly = roomCoordinator.readOnly(rid, Users.findOne({ _id: Meteor.userId() }, { fields: { username: 1 } })); + const isArchived = roomCoordinator.archived(rid) || (subscription && subscription.t === 'd' && subscription.archived); return !isReadOnly && !isArchived; }, @@ -238,7 +239,7 @@ Template.messageBox.helpers({ return true; } - return roomTypes.verifyCanSendMessage(rid); + return roomCoordinator.verifyCanSendMessage(rid); }, actions() { const actionGroups = messageBox.actions.get(); diff --git a/app/ui-message/client/messageBox/messageBoxNotSubscribed.js b/app/ui-message/client/messageBox/messageBoxNotSubscribed.js index 7f8bb9505a59..29570915ad51 100644 --- a/app/ui-message/client/messageBox/messageBoxNotSubscribed.js +++ b/app/ui-message/client/messageBox/messageBoxNotSubscribed.js @@ -4,21 +4,21 @@ import { Template } from 'meteor/templating'; import { settings } from '../../../settings/client'; import { RoomManager, RoomHistoryManager } from '../../../ui-utils/client'; -import { roomTypes } from '../../../utils/client'; import { hasAllPermission } from '../../../authorization/client'; import './messageBoxNotSubscribed.html'; import { call } from '../../../../client/lib/utils/call'; +import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator'; Template.messageBoxNotSubscribed.helpers({ customTemplate() { - return roomTypes.getNotSubscribedTpl(this.rid); + return roomCoordinator.getRoomTypeConfigById(this.rid)?.notSubscribedTpl; }, canJoinRoom() { - return Meteor.userId() && roomTypes.verifyShowJoinLink(this.rid); + return Meteor.userId() && roomCoordinator.getRoomDirectivesById(this.rid)?.showJoinLink(this.rid); }, roomName() { const room = Session.get(`roomData${this.rid}`); - return roomTypes.getRoomName(room.t, room); + return roomCoordinator.getRoomName(room.t, room); }, isJoinCodeRequired() { const room = Session.get(`roomData${this.rid}`); diff --git a/app/ui-message/client/messageBox/messageBoxReadOnly.js b/app/ui-message/client/messageBox/messageBoxReadOnly.js index a20d93326a6b..79a0fe561df9 100644 --- a/app/ui-message/client/messageBox/messageBoxReadOnly.js +++ b/app/ui-message/client/messageBox/messageBoxReadOnly.js @@ -1,10 +1,10 @@ import { Template } from 'meteor/templating'; -import { roomTypes } from '../../../utils'; import './messageBoxReadOnly.html'; +import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator'; Template.messageBoxReadOnly.helpers({ customTemplate() { - return roomTypes.getReadOnlyTpl(this.rid); + return roomCoordinator.getRoomTypeConfigById(this.rid).readOnlyTpl; }, }); diff --git a/app/ui-message/client/popup/messagePopupChannel.js b/app/ui-message/client/popup/messagePopupChannel.js index 0a2f7967b4be..a37434b4fefe 100644 --- a/app/ui-message/client/popup/messagePopupChannel.js +++ b/app/ui-message/client/popup/messagePopupChannel.js @@ -1,10 +1,10 @@ import { Template } from 'meteor/templating'; -import { roomTypes } from '../../../utils'; import './messagePopupChannel.html'; +import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator'; Template.messagePopupChannel.helpers({ channelIcon() { - return roomTypes.getIcon(this); + return roomCoordinator.getIcon(this); }, }); diff --git a/app/ui-sidenav/client/roomList.js b/app/ui-sidenav/client/roomList.js index 08248d3ba7b2..37e365b386c7 100644 --- a/app/ui-sidenav/client/roomList.js +++ b/app/ui-sidenav/client/roomList.js @@ -3,8 +3,10 @@ import { Template } from 'meteor/templating'; import { callbacks } from '../../../lib/callbacks'; import { ChatSubscription, Rooms, Users, Subscriptions } from '../../models'; -import { UiTextContext, getUserPreference, roomTypes } from '../../utils'; +import { getUserPreference } from '../../utils'; +import { UiTextContext } from '../../../definition/IRoomTypeConfig'; import { settings } from '../../settings'; +import { roomCoordinator } from '../../../client/lib/rooms/roomCoordinator'; Template.roomList.helpers({ rooms() { @@ -119,7 +121,7 @@ Template.roomList.helpers({ if (instance.data.anonymous) { return 'No_channels_yet'; } - return roomTypes.getConfig(instance.data.identifier).getUiText(UiTextContext.NO_ROOMS_SUBSCRIBED) || 'No_channels_yet'; + return roomCoordinator.getRoomDirectives(instance.data.identifier)?.getUiText(UiTextContext.NO_ROOMS_SUBSCRIBED) || 'No_channels_yet'; }, }); diff --git a/app/ui-sidenav/client/sideNav.html b/app/ui-sidenav/client/sideNav.html index 381750b6c57a..37f514f80131 100644 --- a/app/ui-sidenav/client/sideNav.html +++ b/app/ui-sidenav/client/sideNav.html @@ -1,5 +1,5 @@