diff --git a/.docker/latest/Dockerfile b/.docker/Dockerfile
similarity index 96%
rename from .docker/latest/Dockerfile
rename to .docker/Dockerfile
index 77b309ea5830e8d464d598370aef0321da4d31b0..beaccef6157ceb62633a4485062fcab562e44d36 100644
--- a/.docker/latest/Dockerfile
+++ b/.docker/Dockerfile
@@ -1,6 +1,6 @@
 FROM rocketchat/base:4
 
-ENV RC_VERSION latest
+ENV RC_VERSION 0.57.0-develop
 
 MAINTAINER buildmaster@rocket.chat
 
diff --git a/.docker/develop/Dockerfile b/.docker/develop/Dockerfile
deleted file mode 100644
index b14fd89de4d41f1f32603511ffaefae9e80bda9f..0000000000000000000000000000000000000000
--- a/.docker/develop/Dockerfile
+++ /dev/null
@@ -1,38 +0,0 @@
-FROM rocketchat/base:4
-
-ENV RC_VERSION develop
-
-MAINTAINER buildmaster@rocket.chat
-
-RUN set -x \
- && curl -SLf "https://rocket.chat/releases/${RC_VERSION}/download" -o rocket.chat.tgz \
- && curl -SLf "https://rocket.chat/releases/${RC_VERSION}/asc" -o rocket.chat.tgz.asc \
- && mkdir /app \
- && gpg --verify rocket.chat.tgz.asc \
- && mkdir -p /app \
- && tar -zxf rocket.chat.tgz -C /app \
- && rm rocket.chat.tgz rocket.chat.tgz.asc \
- && cd /app/bundle/programs/server \
- && npm install \
- && npm cache clear \
- && chown -R rocketchat:rocketchat /app
-
-USER rocketchat
-
-VOLUME /app/uploads
-
-WORKDIR /app/bundle
-
-# needs a mongoinstance - defaults to container linking with alias 'mongo'
-ENV DEPLOY_METHOD=docker \
-    NODE_ENV=production \
-    MONGO_URL=mongodb://mongo:27017/rocketchat \
-    MONGO_OPLOG_URL=mongodb://mongo:27017/local \
-    HOME=/tmp \
-    PORT=3000 \
-    ROOT_URL=http://localhost:3000 \
-    Accounts_AvatarStorePath=/app/uploads
-
-EXPOSE 3000
-
-CMD ["node", "main.js"]
diff --git a/.docker/release-candidate/Dockerfile b/.docker/release-candidate/Dockerfile
deleted file mode 100644
index 192b8e95c0194a5bdae2bdc18dc304b9ce967112..0000000000000000000000000000000000000000
--- a/.docker/release-candidate/Dockerfile
+++ /dev/null
@@ -1,37 +0,0 @@
-FROM rocketchat/base:4
-
-ENV RC_VERSION release-candidate
-
-MAINTAINER buildmaster@rocket.chat
-
-RUN set -x \
- && curl -SLf "https://rocket.chat/releases/${RC_VERSION}/download" -o rocket.chat.tgz \
- && curl -SLf "https://rocket.chat/releases/${RC_VERSION}/asc" -o rocket.chat.tgz.asc \
- && mkdir /app \
- && gpg --verify rocket.chat.tgz.asc \
- && mkdir -p /app \
- && tar -zxf rocket.chat.tgz -C /app \
- && rm rocket.chat.tgz rocket.chat.tgz.asc \
- && cd /app/bundle/programs/server \
- && npm install \
- && npm cache clear \
- && chown -R rocketchat:rocketchat /app
-
-USER rocketchat
-
-VOLUME /app/uploads
-
-WORKDIR /app/bundle
-
-# needs a mongoinstance - defaults to container linking with alias 'mongo'
-ENV DEPLOY_METHOD=docker \
-    NODE_ENV=production \
-    MONGO_URL=mongodb://mongo:27017/rocketchat \
-    HOME=/tmp \
-    PORT=3000 \
-    ROOT_URL=http://localhost:3000 \
-    Accounts_AvatarStorePath=/app/uploads
-
-EXPOSE 3000
-
-CMD ["node", "main.js"]
diff --git a/.sandstorm/sandstorm-pkgdef.capnp b/.sandstorm/sandstorm-pkgdef.capnp
index 14ee2003b1780a2fb399406ffd9f6c1fa64bf459..c794307a7de6917a4eb18b8951ad8053743c6d8b 100644
--- a/.sandstorm/sandstorm-pkgdef.capnp
+++ b/.sandstorm/sandstorm-pkgdef.capnp
@@ -21,7 +21,7 @@ const pkgdef :Spk.PackageDefinition = (
 
 		appVersion = 62,  # Increment this for every release.
 
-		appMarketingVersion = (defaultText = "0.56.0-develop"),
+		appMarketingVersion = (defaultText = "0.57.0-develop"),
 		# Human-readable representation of appVersion. Should match the way you
 		# identify versions of your app in documentation and marketing.
 
diff --git a/.scripts/set-version.js b/.scripts/set-version.js
new file mode 100644
index 0000000000000000000000000000000000000000..c1e1264ae468a560e2039043dd9c5dafa664c7e1
--- /dev/null
+++ b/.scripts/set-version.js
@@ -0,0 +1,190 @@
+/* eslint object-shorthand: 0, prefer-template: 0 */
+
+const path = require('path');
+const fs = require('fs');
+const semver = require('semver');
+const inquirer = require('inquirer');
+const execSync = require('child_process').execSync;
+const git = require('simple-git')(process.cwd());
+
+
+let pkgJson = {};
+
+try {
+	pkgJson = require(path.resolve(
+		process.cwd(),
+		'./package.json'
+	));
+} catch (err) {
+	console.error('no root package.json found');
+}
+
+const files = [
+	'./package.json',
+	'./.sandstorm/sandstorm-pkgdef.capnp',
+	'./.travis/snap.sh',
+	'./.docker/Dockerfile',
+	'./packages/rocketchat-lib/rocketchat.info'
+];
+
+class Actions {
+	static release_rc() {
+		function processVersion(version) {
+			// console.log('Updating files to version ' + version);
+
+			files.forEach(function(file) {
+				const data = fs.readFileSync(file, 'utf8');
+
+				fs.writeFileSync(file, data.replace(pkgJson.version, version), 'utf8');
+			});
+
+			execSync('conventional-changelog --config .github/changelog.js -i HISTORY.md -s');
+
+			inquirer.prompt([{
+				type: 'confirm',
+				message: 'Commit files?',
+				name: 'commit'
+			}]).then(function(answers) {
+				if (!answers.commit) {
+					return;
+				}
+
+				git.status((error, status) => {
+					inquirer.prompt([{
+						type: 'checkbox',
+						message: 'Select files to commit?',
+						name: 'files',
+						choices: status.files.map(file => { return {name: `${ file.working_dir } ${ file.path }`, checked: true}; })
+					}]).then(function(answers) {
+						if (answers.files.length) {
+							git.add(answers.files.map(file => file.slice(2)), () => {
+								git.commit(`Bump version to ${ version }`, () => {
+									inquirer.prompt([{
+										type: 'confirm',
+										message: `Add tag ${ version }?`,
+										name: 'tag'
+									}]).then(function(answers) {
+										if (answers.tag) {
+											// TODO: Add annotated tag
+											git.addTag(version);
+											// TODO: Push
+											// Useg GitHub api to create the release with history
+										}
+									});
+								});
+							});
+						}
+					});
+				});
+			});
+		}
+
+
+		inquirer.prompt([{
+			type: 'list',
+			message: `The current version is ${ pkgJson.version }. Update to version:`,
+			name: 'version',
+			choices: [
+				semver.inc(pkgJson.version, 'prerelease', 'rc'),
+				// semver.inc(pkgJson.version, 'patch'),
+				'custom'
+			]
+		}]).then(function(answers) {
+			if (answers.version === 'custom') {
+				inquirer.prompt([{
+					name: 'version',
+					message: 'Enter your custom version:'
+				}]).then(function(answers) {
+					processVersion(answers.version);
+				});
+			} else {
+				processVersion(answers.version);
+			}
+		});
+	}
+
+	static release_gm() {
+		function processVersion(version) {
+			// console.log('Updating files to version ' + version);
+
+			files.forEach(function(file) {
+				const data = fs.readFileSync(file, 'utf8');
+
+				fs.writeFileSync(file, data.replace(pkgJson.version, version), 'utf8');
+			});
+
+			execSync('conventional-changelog --config .github/changelog.js -i HISTORY.md -s');
+			// TODO improve HISTORY generation for GM
+
+			inquirer.prompt([{
+				type: 'confirm',
+				message: 'Commit files?',
+				name: 'commit'
+			}]).then(function(answers) {
+				if (!answers.commit) {
+					return;
+				}
+
+				git.status((error, status) => {
+					inquirer.prompt([{
+						type: 'checkbox',
+						message: 'Select files to commit?',
+						name: 'files',
+						choices: status.files.map(file => { return {name: `${ file.working_dir } ${ file.path }`, checked: true}; })
+					}]).then(function(answers) {
+						if (answers.files.length) {
+							git.add(answers.files.map(file => file.slice(2)), () => {
+								git.commit(`Bump version to ${ version }`, () => {
+									inquirer.prompt([{
+										type: 'confirm',
+										message: `Add tag ${ version }?`,
+										name: 'tag'
+									}]).then(function(answers) {
+										if (answers.tag) {
+											// TODO: Add annotated tag
+											git.addTag(version);
+											// TODO: Push
+											// Useg GitHub api to create the release with history
+										}
+									});
+								});
+							});
+						}
+					});
+				});
+			});
+		}
+
+
+		inquirer.prompt([{
+			type: 'list',
+			message: `The current version is ${ pkgJson.version }. Update to version:`,
+			name: 'version',
+			choices: [
+				semver.inc(pkgJson.version, 'patch'),
+				'custom'
+			]
+		}]).then(function(answers) {
+			if (answers.version === 'custom') {
+				inquirer.prompt([{
+					name: 'version',
+					message: 'Enter your custom version:'
+				}]).then(function(answers) {
+					processVersion(answers.version);
+				});
+			} else {
+				processVersion(answers.version);
+			}
+		});
+	}
+}
+
+git.status((err, status) => {
+	if (status.current === 'release-candidate') {
+		Actions.release_rc();
+	} else if (status.current === 'master') {
+		Actions.release_gm();
+	} else {
+		console.log(`No release action for branch ${ status.current }`);
+	}
+});
diff --git a/.scripts/version.js b/.scripts/version.js
index a99cf279946879f0e0af948e30b6c342d229c9d5..c4aeb68acd0f2a454578c6903dd2ffef72ae722e 100644
--- a/.scripts/version.js
+++ b/.scripts/version.js
@@ -1,8 +1,7 @@
 /* eslint object-shorthand: 0, prefer-template: 0 */
 
 const path = require('path');
-const fs = require('fs');
-let pkgJson = {};
+var pkgJson = {};
 
 try {
 	pkgJson = require(path.resolve(
@@ -13,35 +12,4 @@ try {
 	console.error('no root package.json found');
 }
 
-const readline = require('readline');
-
-const rl = readline.createInterface({
-	input: process.stdin,
-	output: process.stdout
-});
-
-const files = [
-	'./package.json',
-	'./.sandstorm/sandstorm-pkgdef.capnp',
-	'./.travis/snap.sh',
-	'./packages/rocketchat-lib/rocketchat.info'
-];
-
-console.log('Current version:', pkgJson.version);
-rl.question('New version: ', function(version) {
-	rl.close();
-
-	version = version.trim();
-
-	if (version === '') {
-		return;
-	}
-
-	console.log('Updating files to version ' + version);
-
-	files.forEach(function(file) {
-		const data = fs.readFileSync(file, 'utf8');
-
-		fs.writeFileSync(file, data.replace(pkgJson.version, version), 'utf8');
-	});
-});
+console.log(pkgJson.version);
diff --git a/.travis.yml b/.travis.yml
index bcb12be39a55db180baed1743cfb2ea4627a194b..948ec8f880c734bcd2ac68fbce88a8e903c15f03 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,7 +5,6 @@ services:
 branches:
   only:
   - develop
-  - release-candidate
   - "/^\\d+\\.\\d+\\.\\d+(-rc\\.\\d+)?$/"
 git:
   depth: 1
@@ -74,16 +73,24 @@ before_deploy:
 - ".travis/namefiles.sh"
 - echo ".travis/sandstorm.sh"
 deploy:
-  provider: s3
-  access_key_id: AKIAIKIA7H7D47KUHYCA
-  secret_access_key: "$ACCESSKEY"
-  bucket: download.rocket.chat
-  skip_cleanup: true
-  upload_dir: build
-  local_dir: "$ROCKET_DEPLOY_DIR"
-  on:
-    condition: "$TRAVIS_PULL_REQUEST=false"
-    all_branches: true
+  - provider: s3
+    access_key_id: AKIAIKIA7H7D47KUHYCA
+    secret_access_key: "$ACCESSKEY"
+    bucket: download.rocket.chat
+    skip_cleanup: true
+    upload_dir: build
+    local_dir: "$ROCKET_DEPLOY_DIR"
+    on:
+      condition: "$TRAVIS_PULL_REQUEST=false"
+      all_branches: true
+  # - provider: releases
+  #   api-key: "$GITHUB_TOKEN"
+  #   file_glob: true
+  #   file: build/*
+  #   skip_cleanup: true
+  #   on:
+  #     tags: true
+
 after_deploy:
 - ".travis/docker.sh"
 - ".travis/update-releases.sh"
diff --git a/.travis/setartname.sh b/.travis/setartname.sh
index cf8f9a150c9a00d168854f2442dfec92ebe43c85..35ba8056881a895d234011429ff63a2eb397fec7 100755
--- a/.travis/setartname.sh
+++ b/.travis/setartname.sh
@@ -1,6 +1,6 @@
-if [[ $TRAVIS_TAG ]]
+if [[ $TRAVIS_BRANCH ]]
  then
-  export ARTIFACT_NAME="$TRAVIS_TAG";
+  export ARTIFACT_NAME="$(meteor npm run version --silent).$TRAVIS_BUILD_NUMBER"
 else
-  export ARTIFACT_NAME="$TRAVIS_BRANCH";
+  export ARTIFACT_NAME="$(meteor npm run version --silent)"
 fi
diff --git a/.travis/snap.sh b/.travis/snap.sh
index d5ad25ba5c6805828b8d0d7f250f2be024cf7ee0..8b602fa558c03cee5e58c6c374cde8794cb63d24 100755
--- a/.travis/snap.sh
+++ b/.travis/snap.sh
@@ -17,7 +17,7 @@ elif [[ $TRAVIS_TAG ]]; then
     RC_VERSION=$TRAVIS_TAG
 else
     CHANNEL=edge
-    RC_VERSION=0.56.0-develop
+    RC_VERSION=0.57.0-develop
 fi
 
 echo "Preparing to trigger a snap release for $CHANNEL channel"
diff --git a/HISTORY.md b/HISTORY.md
index a286b0d5590e65f494efeb5d6d0d664d4125b08e..4e732d59c737c668eb5679b4047dc6e80db3df0d 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,29 +1,199 @@
-<a name="0.56.0-develop"></a>
-# 0.56.0-develop (2017-04-18)
-
+<a name="0.56.0"></a>
+# 0.56.0 (2017-05-15)
 
 ### New Features
 
+- [#6881](https://github.com/RocketChat/Rocket.Chat/pull/6881) Add a pointer cursor to message images
 - [#6615](https://github.com/RocketChat/Rocket.Chat/pull/6615) Add a setting to not run outgoing integrations on message edits
+- [#5373](https://github.com/RocketChat/Rocket.Chat/pull/5373) Add option on Channel Settings: Hide Notifications and Hide Unread Room Status ([#2707](https://github.com/RocketChat/Rocket.Chat/issue/2707), [#2143](https://github.com/RocketChat/Rocket.Chat/issue/2143))
+- [#6807](https://github.com/RocketChat/Rocket.Chat/pull/6807) create a method 'create token'
+- [#6827](https://github.com/RocketChat/Rocket.Chat/pull/6827) Make channels.info accept roomName, just like groups.info
+- [#6797](https://github.com/RocketChat/Rocket.Chat/pull/6797) Option to allow to signup as anonymous
+- [#6722](https://github.com/RocketChat/Rocket.Chat/pull/6722) Remove lesshat
+- [#6842](https://github.com/RocketChat/Rocket.Chat/pull/6842) Snap ARM support
 - [#6692](https://github.com/RocketChat/Rocket.Chat/pull/6692) Use tokenSentVia parameter for clientid/secret to token endpoint
+- [#6940](https://github.com/RocketChat/Rocket.Chat/pull/6940) Add SMTP settings for Protocol and Pool
+- [#6938](https://github.com/RocketChat/Rocket.Chat/pull/6938) Improve CI/Docker build/release
+- [#6953](https://github.com/RocketChat/Rocket.Chat/pull/6953) Show info about multiple instances at admin page
 
 
 ### Bug Fixes
 
+- [#6845](https://github.com/RocketChat/Rocket.Chat/pull/6845) Added helper for testing if the current user matches the params
+- [#6737](https://github.com/RocketChat/Rocket.Chat/pull/6737) Archiving Direct Messages
+- [#6734](https://github.com/RocketChat/Rocket.Chat/pull/6734) Bug with incoming integration (0.55.1)
+- [#6768](https://github.com/RocketChat/Rocket.Chat/pull/6768) CSV importer: require that there is some data in the zip, not ALL data
 - [#6709](https://github.com/RocketChat/Rocket.Chat/pull/6709) emoji picker exception
+- [#6721](https://github.com/RocketChat/Rocket.Chat/pull/6721) Fix Caddy by forcing go 1.7 as needed by one of caddy's dependencies
+- [#6798](https://github.com/RocketChat/Rocket.Chat/pull/6798) Fix iframe wise issues
+- [#6704](https://github.com/RocketChat/Rocket.Chat/pull/6704) Fix message types
+- [#6760](https://github.com/RocketChat/Rocket.Chat/pull/6760) Hides nav buttons when selecting own profile
+- [#6747](https://github.com/RocketChat/Rocket.Chat/pull/6747) Incorrect error message when creating channel
+- [#6800](https://github.com/RocketChat/Rocket.Chat/pull/6800) Quoted and replied messages not retaining the original message's alias
+- [#6796](https://github.com/RocketChat/Rocket.Chat/pull/6796) REST API user.update throwing error due to rate limiting
+- [#6767](https://github.com/RocketChat/Rocket.Chat/pull/6767) Search full name on client side
+- [#6758](https://github.com/RocketChat/Rocket.Chat/pull/6758) Sort by real name if use real name setting is enabled
+- [#6861](https://github.com/RocketChat/Rocket.Chat/pull/6861) start/unstar message
+- [#6896](https://github.com/RocketChat/Rocket.Chat/pull/6896) Users status on main menu always offline
+- [#6923](https://github.com/RocketChat/Rocket.Chat/pull/6923) Not showing unread count on electron app’s icon
+- [#6939](https://github.com/RocketChat/Rocket.Chat/pull/6939) Compile CSS color variables
+- [#6935](https://github.com/RocketChat/Rocket.Chat/pull/6935) Error when trying to show preview of undefined filetype
+- [#6955](https://github.com/RocketChat/Rocket.Chat/pull/6955) Remove spaces from env PORT and INSTANCE_IP
+- [#6968](https://github.com/RocketChat/Rocket.Chat/pull/6968) make channels.create API check for create-c
+
+
+<details>
+<summary>Others</summary>
+
+- [#5986](https://github.com/RocketChat/Rocket.Chat/pull/5986) Anonymous use
+- [#6368](https://github.com/RocketChat/Rocket.Chat/pull/6368) Breaking long URLS to prevent overflow
+- [#6671](https://github.com/RocketChat/Rocket.Chat/pull/6671) Convert Katex Package to Js
+- [#6780](https://github.com/RocketChat/Rocket.Chat/pull/6780) Convert Mailer Package to Js
+- [#6694](https://github.com/RocketChat/Rocket.Chat/pull/6694) Convert markdown to js
+- [#6689](https://github.com/RocketChat/Rocket.Chat/pull/6689) Convert Mentions-Flextab Package to Js
+- [#6781](https://github.com/RocketChat/Rocket.Chat/pull/6781) Convert Message-Star Package to js 
+- [#6688](https://github.com/RocketChat/Rocket.Chat/pull/6688) Convert Oembed Package to Js
+- [#6672](https://github.com/RocketChat/Rocket.Chat/pull/6672) Converted rocketchat-lib 3
+- [#6654](https://github.com/RocketChat/Rocket.Chat/pull/6654) disable proxy configuration
+- [#6816](https://github.com/RocketChat/Rocket.Chat/pull/6816) LingoHub based on develop
+- [#6715](https://github.com/RocketChat/Rocket.Chat/pull/6715) LingoHub based on develop
+- [#6703](https://github.com/RocketChat/Rocket.Chat/pull/6703) LingoHub based on develop
+- [#6858](https://github.com/RocketChat/Rocket.Chat/pull/6858) Meteor update
+- [#6706](https://github.com/RocketChat/Rocket.Chat/pull/6706) meteor update to 1.4.4
+- [#6804](https://github.com/RocketChat/Rocket.Chat/pull/6804) Missing useful fields in admin user list [#5110](https://github.com/RocketChat/Rocket.Chat/issue/5110)
+- [#6593](https://github.com/RocketChat/Rocket.Chat/pull/6593) Rocketchat lib2
+</details>
+
+
+
+<details>
+<summary>Details</summary>
+
+## 0.56.0-rc.7 (2017-05-15)
+
+
+### Bug Fixes
+
+- [#6968](https://github.com/RocketChat/Rocket.Chat/pull/6968) make channels.create API check for create-c
+
+
+## 0.56.0-rc.5 (2017-05-11)
+
+
+### Bug Fixes
+
+- [#6935](https://github.com/RocketChat/Rocket.Chat/pull/6935) Error when trying to show preview of undefined filetype
+- [#6955](https://github.com/RocketChat/Rocket.Chat/pull/6955) Remove spaces from env PORT and INSTANCE_IP
+
+
+## 0.56.0-rc.4 (2017-05-11)
+
+
+### New Features
+
+- [#6953](https://github.com/RocketChat/Rocket.Chat/pull/6953) Show info about multiple instances at admin page
+
+
+## 0.56.0-rc.3 (2017-05-10)
+
+
+### New Features
+
+- [#6940](https://github.com/RocketChat/Rocket.Chat/pull/6940) Add SMTP settings for Protocol and Pool
+- [#6938](https://github.com/RocketChat/Rocket.Chat/pull/6938) Improve CI/Docker build/release
+
+
+### Bug Fixes
+
+- [#6939](https://github.com/RocketChat/Rocket.Chat/pull/6939) Compile CSS color variables
+
+
+## 0.56.0-rc.2 (2017-05-09)
+
+
+### Bug Fixes
+
+- [#6923](https://github.com/RocketChat/Rocket.Chat/pull/6923) Not showing unread count on electron app’s icon
+
+
+## 0.56.0-rc.1 (2017-05-05)
+
+
+### Bug Fixes
+
+- [#6896](https://github.com/RocketChat/Rocket.Chat/pull/6896) Users status on main menu always offline
+
+
+## 0.56.0-rc.0 (2017-05-04)
+
+
+### New Features
+
+- [#6881](https://github.com/RocketChat/Rocket.Chat/pull/6881) Add a pointer cursor to message images
+- [#6615](https://github.com/RocketChat/Rocket.Chat/pull/6615) Add a setting to not run outgoing integrations on message edits
+- [#5373](https://github.com/RocketChat/Rocket.Chat/pull/5373) Add option on Channel Settings: Hide Notifications and Hide Unread Room Status ([#2707](https://github.com/RocketChat/Rocket.Chat/issue/2707), [#2143](https://github.com/RocketChat/Rocket.Chat/issue/2143))
+- [#6807](https://github.com/RocketChat/Rocket.Chat/pull/6807) create a method 'create token'
+- [#6827](https://github.com/RocketChat/Rocket.Chat/pull/6827) Make channels.info accept roomName, just like groups.info
+- [#6797](https://github.com/RocketChat/Rocket.Chat/pull/6797) Option to allow to signup as anonymous
+- [#6722](https://github.com/RocketChat/Rocket.Chat/pull/6722) Remove lesshat
+- [#6842](https://github.com/RocketChat/Rocket.Chat/pull/6842) Snap ARM support
+- [#6692](https://github.com/RocketChat/Rocket.Chat/pull/6692) Use tokenSentVia parameter for clientid/secret to token endpoint
+
+
+### Bug Fixes
+
+- [#6845](https://github.com/RocketChat/Rocket.Chat/pull/6845) Added helper for testing if the current user matches the params
+- [#6737](https://github.com/RocketChat/Rocket.Chat/pull/6737) Archiving Direct Messages
+- [#6734](https://github.com/RocketChat/Rocket.Chat/pull/6734) Bug with incoming integration (0.55.1)
+- [#6768](https://github.com/RocketChat/Rocket.Chat/pull/6768) CSV importer: require that there is some data in the zip, not ALL data
 - [#6709](https://github.com/RocketChat/Rocket.Chat/pull/6709) emoji picker exception
+- [#6721](https://github.com/RocketChat/Rocket.Chat/pull/6721) Fix Caddy by forcing go 1.7 as needed by one of caddy's dependencies
+- [#6798](https://github.com/RocketChat/Rocket.Chat/pull/6798) Fix iframe wise issues
 - [#6704](https://github.com/RocketChat/Rocket.Chat/pull/6704) Fix message types
+- [#6760](https://github.com/RocketChat/Rocket.Chat/pull/6760) Hides nav buttons when selecting own profile
+- [#6747](https://github.com/RocketChat/Rocket.Chat/pull/6747) Incorrect error message when creating channel
+- [#6800](https://github.com/RocketChat/Rocket.Chat/pull/6800) Quoted and replied messages not retaining the original message's alias
+- [#6796](https://github.com/RocketChat/Rocket.Chat/pull/6796) REST API user.update throwing error due to rate limiting
+- [#6767](https://github.com/RocketChat/Rocket.Chat/pull/6767) Search full name on client side
+- [#6758](https://github.com/RocketChat/Rocket.Chat/pull/6758) Sort by real name if use real name setting is enabled
+- [#6861](https://github.com/RocketChat/Rocket.Chat/pull/6861) start/unstar message
 
 
 <details>
 <summary>Others</summary>
 
+- [#5986](https://github.com/RocketChat/Rocket.Chat/pull/5986) Anonymous use
+- [#6368](https://github.com/RocketChat/Rocket.Chat/pull/6368) Breaking long URLS to prevent overflow
+- [#6671](https://github.com/RocketChat/Rocket.Chat/pull/6671) Convert Katex Package to Js
+- [#6780](https://github.com/RocketChat/Rocket.Chat/pull/6780) Convert Mailer Package to Js
+- [#6694](https://github.com/RocketChat/Rocket.Chat/pull/6694) Convert markdown to js
+- [#6689](https://github.com/RocketChat/Rocket.Chat/pull/6689) Convert Mentions-Flextab Package to Js
+- [#6781](https://github.com/RocketChat/Rocket.Chat/pull/6781) Convert Message-Star Package to js 
+- [#6688](https://github.com/RocketChat/Rocket.Chat/pull/6688) Convert Oembed Package to Js
+- [#6672](https://github.com/RocketChat/Rocket.Chat/pull/6672) Converted rocketchat-lib 3
+- [#6654](https://github.com/RocketChat/Rocket.Chat/pull/6654) disable proxy configuration
+- [#6816](https://github.com/RocketChat/Rocket.Chat/pull/6816) LingoHub based on develop
+- [#6715](https://github.com/RocketChat/Rocket.Chat/pull/6715) LingoHub based on develop
 - [#6703](https://github.com/RocketChat/Rocket.Chat/pull/6703) LingoHub based on develop
+- [#6858](https://github.com/RocketChat/Rocket.Chat/pull/6858) Meteor update
 - [#6706](https://github.com/RocketChat/Rocket.Chat/pull/6706) meteor update to 1.4.4
+- [#6804](https://github.com/RocketChat/Rocket.Chat/pull/6804) Missing useful fields in admin user list [#5110](https://github.com/RocketChat/Rocket.Chat/issue/5110)
+- [#6593](https://github.com/RocketChat/Rocket.Chat/pull/6593) Rocketchat lib2
+</details>
+
 </details>
 
 
 
+<a name="0.55.1"></a>
+## 0.55.1 (2017-04-19)
+
+
+### Bug Fixes
+
+- [#6734](https://github.com/RocketChat/Rocket.Chat/pull/6734) Bug with incoming integration (0.55.1)
+
+
 <a name="0.55.0"></a>
 # 0.55.0 (2017-04-18)
 
diff --git a/package.json b/package.json
index ec9b0a5a746f38616e617fe4fbd008d905bfea9b..57e35a696e7384ef8627163b81897c37945b7f6d 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
 	"name": "Rocket.Chat",
 	"description": "The Ultimate Open Source WebChat Platform",
-	"version": "0.56.0-develop",
+	"version": "0.57.0-develop",
 	"author": {
 		"name": "Rocket.Chat",
 		"url": "https://rocket.chat/"
@@ -56,7 +56,8 @@
 		"coverage": "nyc -r html mocha --opts ./mocha.opts \"`node -e \"console.log(require('./package.json').mocha.tests.join(' '))\"`\"",
 		"testunit": "mocha --opts ./mocha.opts \"`node -e \"console.log(require('./package.json').mocha.tests.join(' '))\"`\"",
 		"version": "node .scripts/version.js",
-		"release": "npm run version && conventional-changelog --config .github/changelog.js -i HISTORY.md -s"
+		"set-version": "node .scripts/set-version.js",
+		"release": "npm run set-version --silent"
 	},
 	"license": "MIT",
 	"repository": {
@@ -68,11 +69,9 @@
 		"email": "support@rocket.chat"
 	},
 	"devDependencies": {
-    "babel-mocha-es6-compiler": "^0.1.0",
-    "babel-plugin-array-includes": "^2.0.3",
-		"chimp": "^0.48.0",
-		"conventional-changelog": "^1.1.3",
-    "babel-mocha-es6-compiler": "^0.1.0",
+		"chimp": "^0.49.0",
+        "babel-mocha-es6-compiler": "^0.1.0",
+        "babel-plugin-array-includes": "^2.0.3",
 		"eslint": "^3.19.0",
 		"stylelint": "^7.10.1",
 		"supertest": "^3.0.0"
@@ -84,7 +83,7 @@
 		"file-type": "^4.3.0",
 		"highlight.js": "^9.11.0",
 		"jquery": "^3.2.1",
-		"mime-db": "^1.27.0",
+		"mime-db": "^1.28.0",
 		"mime-type": "^3.0.4",
 		"moment": "^2.18.1",
 		"moment-timezone": "^0.5.13",
diff --git a/packages/meteor-accounts-saml/saml_utils.js b/packages/meteor-accounts-saml/saml_utils.js
index 5d8396e88eca33163aea4136d608eed2b3ec1f3c..02660f2dcdc8657374445f65fcd176cb2efa7118 100644
--- a/packages/meteor-accounts-saml/saml_utils.js
+++ b/packages/meteor-accounts-saml/saml_utils.js
@@ -432,12 +432,38 @@ SAML.prototype.validateResponse = function(samlResponse, relayState, callback) {
 let decryptionCert;
 SAML.prototype.generateServiceProviderMetadata = function(callbackUrl) {
 
-	let keyDescriptor = null;
-
 	if (!decryptionCert) {
 		decryptionCert = this.options.privateCert;
 	}
 
+	if (!this.options.callbackUrl && !callbackUrl) {
+		throw new Error(
+			'Unable to generate service provider metadata when callbackUrl option is not set');
+	}
+
+	const metadata = {
+		'EntityDescriptor': {
+			'@xmlns': 'urn:oasis:names:tc:SAML:2.0:metadata',
+			'@xmlns:ds': 'http://www.w3.org/2000/09/xmldsig#',
+			'@entityID': this.options.issuer,
+			'SPSSODescriptor': {
+				'@protocolSupportEnumeration': 'urn:oasis:names:tc:SAML:2.0:protocol',
+				'SingleLogoutService': {
+					'@Binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
+					'@Location': `${ Meteor.absoluteUrl() }_saml/logout/${ this.options.provider }/`,
+					'@ResponseLocation': `${ Meteor.absoluteUrl() }_saml/logout/${ this.options.provider }/`
+				},
+				'NameIDFormat': this.options.identifierFormat,
+				'AssertionConsumerService': {
+					'@index': '1',
+					'@isDefault': 'true',
+					'@Binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
+					'@Location': callbackUrl
+				}
+			}
+		}
+	};
+
 	if (this.options.privateKey) {
 		if (!decryptionCert) {
 			throw new Error(
@@ -448,7 +474,7 @@ SAML.prototype.generateServiceProviderMetadata = function(callbackUrl) {
 		decryptionCert = decryptionCert.replace(/-+END CERTIFICATE-+\r?\n?/, '');
 		decryptionCert = decryptionCert.replace(/\r\n/g, '\n');
 
-		keyDescriptor = {
+		metadata['EntityDescriptor']['SPSSODescriptor']['KeyDescriptor'] = {
 			'ds:KeyInfo': {
 				'ds:X509Data': {
 					'ds:X509Certificate': {
@@ -477,35 +503,6 @@ SAML.prototype.generateServiceProviderMetadata = function(callbackUrl) {
 		};
 	}
 
-	if (!this.options.callbackUrl && !callbackUrl) {
-		throw new Error(
-			'Unable to generate service provider metadata when callbackUrl option is not set');
-	}
-
-	const metadata = {
-		'EntityDescriptor': {
-			'@xmlns': 'urn:oasis:names:tc:SAML:2.0:metadata',
-			'@xmlns:ds': 'http://www.w3.org/2000/09/xmldsig#',
-			'@entityID': this.options.issuer,
-			'SPSSODescriptor': {
-				'@protocolSupportEnumeration': 'urn:oasis:names:tc:SAML:2.0:protocol',
-				'KeyDescriptor': keyDescriptor,
-				'SingleLogoutService': {
-					'@Binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
-					'@Location': `${ Meteor.absoluteUrl() }_saml/logout/${ this.options.provider }/`,
-					'@ResponseLocation': `${ Meteor.absoluteUrl() }_saml/logout/${ this.options.provider }/`
-				},
-				'NameIDFormat': this.options.identifierFormat,
-				'AssertionConsumerService': {
-					'@index': '1',
-					'@isDefault': 'true',
-					'@Binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
-					'@Location': callbackUrl
-				}
-			}
-		}
-	};
-
 	return xmlbuilder.create(metadata).end({
 		pretty: true,
 		indent: '  ',
diff --git a/packages/meteor-autocomplete/autocomplete-client.coffee b/packages/meteor-autocomplete/autocomplete-client.coffee
deleted file mode 100755
index b58875785139042817b8edf03b41f032651c88c2..0000000000000000000000000000000000000000
--- a/packages/meteor-autocomplete/autocomplete-client.coffee
+++ /dev/null
@@ -1,367 +0,0 @@
-AutoCompleteRecords = new Mongo.Collection("autocompleteRecords")
-
-isServerSearch = (rule) -> _.isString(rule.collection)
-
-validateRule = (rule) ->
-  if rule.subscription? and not Match.test(rule.collection, String)
-    throw new Error("Collection name must be specified as string for server-side search")
-
-  # XXX back-compat message, to be removed
-  if rule.callback?
-    console.warn("autocomplete no longer supports callbacks; use event listeners instead.")
-
-isWholeField = (rule) ->
-  # either '' or null both count as whole field.
-  return !rule.token
-
-getRegExp = (rule) ->
-  unless isWholeField(rule)
-    # Expressions for the range from the last word break to the current cursor position
-    new RegExp('(^|\\b|\\s)' + rule.token + '([\\w.]*)$')
-  else
-    # Whole-field behavior - word characters or spaces
-    new RegExp('(^)(.*)$')
-
-getFindParams = (rule, filter, limit) ->
-  # This is a different 'filter' - the selector from the settings
-  # We need to extend so that we don't copy over rule.filter
-  selector = _.extend({}, rule.filter || {})
-  options = { limit: limit }
-
-  # Match anything, no sort, limit X
-  return [ selector, options ] unless filter
-
-  if rule.sort and rule.field
-    sortspec = {}
-    # Only sort if there is a filter, for faster performance on a match of anything
-    sortspec[rule.field] = 1
-    options.sort = sortspec
-
-  if _.isFunction(rule.selector)
-    # Custom selector
-    _.extend(selector, rule.selector(filter))
-  else
-    selector[rule.field] = {
-      $regex: if rule.matchAll then filter else "^" + filter
-      # default is case insensitive search - empty string is not the same as undefined!
-      $options: if (typeof rule.options is 'undefined') then 'i' else rule.options
-    }
-
-  return [ selector, options ]
-
-getField = (obj, str) ->
-  obj = obj[key] for key in str.split(".")
-  return obj
-
-class @AutoComplete
-
-  @KEYS: [
-    40, # DOWN
-    38, # UP
-    13, # ENTER
-    27, # ESCAPE
-    9   # TAB
-  ]
-
-  constructor: (settings) ->
-    @limit = settings.limit || 5
-    @position = settings.position || "bottom"
-
-    @rules = settings.rules
-    validateRule(rule) for rule in @rules
-
-    @expressions = (getRegExp(rule) for rule in @rules)
-
-    @matched = -1
-    @loaded = true
-
-    # Reactive dependencies for current matching rule and filter
-    @ruleDep = new Deps.Dependency
-    @filterDep = new Deps.Dependency
-    @loadingDep = new Deps.Dependency
-
-    # autosubscribe to the record set published by the server based on the filter
-    # This will tear down server subscriptions when they are no longer being used.
-    @sub = null
-    @comp = Deps.autorun =>
-      # Stop any existing sub immediately, don't wait
-      @sub?.stop()
-
-      return unless (rule = @matchedRule()) and (filter = @getFilter()) isnt null
-
-      # subscribe only for server-side collections
-      unless isServerSearch(rule)
-        @setLoaded(true) # Immediately loaded
-        return
-
-      [ selector, options ] = getFindParams(rule, filter, @limit)
-
-      # console.debug 'Subscribing to <%s> in <%s>.<%s>', filter, rule.collection, rule.field
-      @setLoaded(false)
-      subName = rule.subscription || "autocomplete-recordset"
-      @sub = Meteor.subscribe(subName,
-        selector, options, rule.collection, => @setLoaded(true))
-
-  teardown: ->
-    # Stop the reactive computation we started for this autocomplete instance
-    @comp.stop()
-
-  # reactive getters and setters for @filter and the currently matched rule
-  matchedRule: ->
-    @ruleDep.depend()
-    if @matched >= 0 then @rules[@matched] else null
-
-  setMatchedRule: (i) ->
-    @matched = i
-    @ruleDep.changed()
-
-  getFilter: ->
-    @filterDep.depend()
-    return @filter
-
-  setFilter: (x) ->
-    @filter = x
-    @filterDep.changed()
-    return @filter
-
-  isLoaded: ->
-    @loadingDep.depend()
-    return @loaded
-
-  setLoaded: (val) ->
-    return if val is @loaded # Don't cause redraws unnecessarily
-    @loaded = val
-    @loadingDep.changed()
-
-  onKeyUp: ->
-    return unless @$element # Don't try to do this while loading
-    startpos = @element.selectionStart
-    val = @getText().substring(0, startpos)
-
-    ###
-      Matching on multiple expressions.
-      We always go from a matched state to an unmatched one
-      before going to a different matched one.
-    ###
-    i = 0
-    breakLoop = false
-    while i < @expressions.length
-      matches = val.match(@expressions[i])
-
-      # matching -> not matching
-      if not matches and @matched is i
-        @setMatchedRule(-1)
-        breakLoop = true
-
-      # not matching -> matching
-      if matches and @matched is -1
-        @setMatchedRule(i)
-        breakLoop = true
-
-      # Did filter change?
-      if matches and @filter isnt matches[2]
-        @setFilter(matches[2])
-        breakLoop = true
-
-      break if breakLoop
-      i++
-
-  onKeyDown: (e) ->
-    return if @matched is -1 or (@constructor.KEYS.indexOf(e.keyCode) < 0)
-
-    switch e.keyCode
-      when 9, 13 # TAB, ENTER
-        if @select() # Don't jump fields or submit if select successful
-          e.preventDefault()
-          e.stopPropagation()
-      # preventDefault needed below to avoid moving cursor when selecting
-      when 40 # DOWN
-        e.preventDefault()
-        @next()
-      when 38 # UP
-        e.preventDefault()
-        @prev()
-      when 27 # ESCAPE
-        @$element.blur()
-        @hideList()
-
-    return
-
-  onFocus: ->
-    # We need to run onKeyUp after the focus resolves,
-    # or the caret position (selectionStart) will not be correct
-    Meteor.defer => @onKeyUp()
-
-  onBlur: ->
-    # We need to delay this so click events work
-    # TODO this is a bit of a hack; see if we can't be smarter
-    Meteor.setTimeout =>
-      @hideList()
-    , 500
-
-  onItemClick: (doc, e) => @processSelection(doc, @rules[@matched])
-
-  onItemHover: (doc, e) ->
-    @tmplInst.$(".-autocomplete-item").removeClass("selected")
-    $(e.target).closest(".-autocomplete-item").addClass("selected")
-
-  filteredList: ->
-    # @ruleDep.depend() # optional as long as we use depend on filter, because list will always get re-rendered
-    filter = @getFilter() # Reactively depend on the filter
-    return null if @matched is -1
-
-    rule = @rules[@matched]
-    # Don't display list unless we have a token or a filter (or both)
-    # Single field: nothing displayed until something is typed
-    return null unless rule.token or filter
-
-    [ selector, options ] = getFindParams(rule, filter, @limit)
-
-    Meteor.defer => @ensureSelection()
-
-    # if server collection, the server has already done the filtering work
-    return AutoCompleteRecords.find({}, options) if isServerSearch(rule)
-
-    # Otherwise, search on client
-    return rule.collection.find(selector, options)
-
-  isShowing: ->
-    rule = @matchedRule()
-    # Same rules as above
-    showing = rule? and (rule.token or @getFilter())
-
-    # Do this after the render
-    if showing
-      Meteor.defer =>
-        @positionContainer()
-        @ensureSelection()
-
-    return showing
-
-  # Replace text with currently selected item
-  select: ->
-    node = @tmplInst.find(".-autocomplete-item.selected")
-    return false unless node?
-    doc = Blaze.getData(node)
-    return false unless doc # Don't select if nothing matched
-
-    @processSelection(doc, @rules[@matched])
-    return true
-
-  processSelection: (doc, rule) ->
-    replacement = getField(doc, rule.field)
-
-    unless isWholeField(rule)
-      @replace(replacement, rule)
-      @hideList()
-
-    else
-      # Empty string or doesn't exist?
-      # Single-field replacement: replace whole field
-      @setText(replacement)
-
-      # Field retains focus, but list is hidden unless another key is pressed
-      # Must be deferred or onKeyUp will trigger and match again
-      # TODO this is a hack; see above
-      @onBlur()
-
-    @$element.trigger("autocompleteselect", doc)
-    return
-
-  # Replace the appropriate region
-  replace: (replacement) ->
-    startpos = @element.selectionStart
-    fullStuff = @getText()
-    val = fullStuff.substring(0, startpos)
-    val = val.replace(@expressions[@matched], "$1" + @rules[@matched].token + replacement)
-    posfix = fullStuff.substring(startpos, fullStuff.length)
-    separator = (if posfix.match(/^\s/) then "" else " ")
-    finalFight = val + separator + posfix
-    @setText finalFight
-
-    newPosition = val.length + 1
-    @element.setSelectionRange(newPosition, newPosition)
-    return
-
-  hideList: ->
-    @setMatchedRule(-1)
-    @setFilter(null)
-
-  getText: ->
-    return @$element.val() || @$element.text()
-
-  setText: (text) ->
-    if @$element.is("input,textarea")
-      @$element.val(text)
-    else
-      @$element.html(text)
-
-  ###
-    Rendering functions
-  ###
-  positionContainer: ->
-    # First render; Pick the first item and set css whenever list gets shown
-    position = @$element.position()
-
-    rule = @matchedRule()
-
-    offset = getCaretCoordinates(@element, @element.selectionStart)
-
-    # In whole-field positioning, we don't move the container and make it the
-    # full width of the field.
-    if rule? and isWholeField(rule)
-      pos =
-        left: position.left
-        width: @$element.outerWidth()               # position.offsetWidth
-    else # Normal positioning, at token word
-      pos =
-        left: position.left + offset.left
-
-    # Position menu from top (above) or from bottom of caret (below, default)
-    if @position is "top"
-      pos.bottom = @$element.offsetParent().height() - position.top - offset.top
-    else
-      pos.top = position.top + offset.top + parseInt(@$element.css('font-size'))
-
-    @tmplInst.$(".-autocomplete-container").css(pos)
-
-  ensureSelection : ->
-    # Re-render; make sure selected item is something in the list or none if list empty
-    selectedItem = @tmplInst.$(".-autocomplete-item.selected")
-
-    unless selectedItem.length
-      # Select anything
-      @tmplInst.$(".-autocomplete-item:first-child").addClass("selected")
-
-  # Select next item in list
-  next: ->
-    currentItem = @tmplInst.$(".-autocomplete-item.selected")
-    return unless currentItem.length # Don't try to iterate an empty list
-    currentItem.removeClass("selected")
-
-    next = currentItem.next()
-    if next.length
-      next.addClass("selected")
-    else # End of list or lost selection; Go back to first item
-      @tmplInst.$(".-autocomplete-item:first-child").addClass("selected")
-
-  # Select previous item in list
-  prev: ->
-    currentItem = @tmplInst.$(".-autocomplete-item.selected")
-    return unless currentItem.length # Don't try to iterate an empty list
-    currentItem.removeClass("selected")
-
-    prev = currentItem.prev()
-    if prev.length
-      prev.addClass("selected")
-    else # Beginning of list or lost selection; Go to end of list
-      @tmplInst.$(".-autocomplete-item:last-child").addClass("selected")
-
-  # This doesn't need to be reactive because list already changes reactively
-  # and will cause all of the items to re-render anyway
-  currentTemplate: -> @rules[@matched].template
-
-AutocompleteTest =
-  records: AutoCompleteRecords
-  getRegExp: getRegExp
-  getFindParams: getFindParams
diff --git a/packages/meteor-autocomplete/autocomplete-server.coffee b/packages/meteor-autocomplete/autocomplete-server.coffee
deleted file mode 100755
index 192d5479bb20a72935a8730bb583391fee7100a6..0000000000000000000000000000000000000000
--- a/packages/meteor-autocomplete/autocomplete-server.coffee
+++ /dev/null
@@ -1,27 +0,0 @@
-class Autocomplete
-  @publishCursor: (cursor, sub) ->
-    # This also attaches an onStop callback to sub, so we don't need to worry about that.
-    # https://github.com/meteor/meteor/blob/devel/packages/mongo/collection.js
-    Mongo.Collection._publishCursor(cursor, sub, "autocompleteRecords")
-
-Meteor.publish 'autocomplete-recordset', (selector, options, collName) ->
-  collection = global[collName]
-  unless collection
-    throw new Error(collName + ' is not defined on the global namespace of the server.')
-
-  # This is a semi-documented Meteor feature:
-  # https://github.com/meteor/meteor/blob/devel/packages/mongo-livedata/collection.js
-  unless collection._isInsecure()
-    Meteor._debug(collName + ' is a secure collection, therefore no data was returned because the client could compromise security by subscribing to arbitrary server collections via the browser console. Please write your own publish function.')
-    return [] # We need this for the subscription to be marked ready
-
-  # guard against client-side DOS: hard limit to 50
-  options.limit = Math.min(50, Math.abs(options.limit)) if options.limit
-
-  # Push this into our own collection on the client so they don't interfere with other publications of the named collection.
-  # This also stops the observer automatically when the subscription is stopped.
-  Autocomplete.publishCursor( collection.find(selector, options), this)
-
-  # Mark the subscription ready after the initial addition of documents.
-  this.ready()
-
diff --git a/packages/meteor-autocomplete/client/autocomplete-client.js b/packages/meteor-autocomplete/client/autocomplete-client.js
new file mode 100755
index 0000000000000000000000000000000000000000..809f2fa77043a736705412ca3b105f05abecb7e5
--- /dev/null
+++ b/packages/meteor-autocomplete/client/autocomplete-client.js
@@ -0,0 +1,449 @@
+/* globals Deps, getCaretCoordinates*/
+const AutoCompleteRecords = new Mongo.Collection('autocompleteRecords');
+
+const isServerSearch = function(rule) {
+	return _.isString(rule.collection);
+};
+
+const validateRule = function(rule) {
+	if (rule.subscription != null && !Match.test(rule.collection, String)) {
+		throw new Error('Collection name must be specified as string for server-side search');
+	}
+	// XXX back-compat message, to be removed
+	if (rule.callback) {
+		console.warn('autocomplete no longer supports callbacks; use event listeners instead.');
+	}
+};
+
+const isWholeField = function(rule) {
+	// either '' or null both count as whole field.
+	return !rule.token;
+};
+
+const getRegExp = function(rule) {
+	if (!isWholeField(rule)) {
+		// Expressions for the range from the last word break to the current cursor position
+		return new RegExp(`(^|\\b|\\s)${ rule.token }([\\w.]*)$`);
+	} else {
+		// Whole-field behavior - word characters or spaces
+		return new RegExp('(^)(.*)$');
+	}
+};
+
+const getFindParams = function(rule, filter, limit) {
+	// This is a different 'filter' - the selector from the settings
+	// We need to extend so that we don't copy over rule.filter
+	const selector = _.extend({}, rule.filter || {});
+	const options = {
+		limit
+	};
+	if (!filter) {
+		// Match anything, no sort, limit X
+		return [selector, options];
+	}
+	if (rule.sort && rule.field) {
+		const sortspec = {};
+		// Only sort if there is a filter, for faster performance on a match of anything
+		sortspec[rule.field] = 1;
+		options.sort = sortspec;
+	}
+	if (_.isFunction(rule.selector)) {
+		// Custom selector
+		_.extend(selector, rule.selector(filter));
+	} else {
+		selector[rule.field] = {
+			$regex: rule.matchAll ? filter : `^${ filter }`,
+			// default is case insensitive search - empty string is not the same as undefined!
+			$options: typeof rule.options === 'undefined' ? 'i' : rule.options
+		};
+	}
+	return [selector, options];
+};
+
+const getField = function(obj, str) {
+	const string = str.split('.');
+	string.forEach(key => {
+		obj = obj[key];
+	});
+	return obj;
+};
+
+this.AutoComplete = class {
+	constructor(settings) {
+		this.KEYS = [40, 38, 13, 27, 9];
+		this.limit = settings.limit || 5;
+		this.position = settings.position || 'bottom';
+		this.rules = settings.rules;
+		const rules = this.rules;
+
+		Object.keys(rules).forEach(key => {
+			const rule = rules[key];
+			validateRule(rule);
+		});
+
+		this.expressions = (() => {
+			return Object.keys(rules).map(key => {
+				const rule = rules[key];
+				return getRegExp(rule);
+			});
+		})();
+		this.matched = -1;
+		this.loaded = true;
+
+		// Reactive dependencies for current matching rule and filter
+		this.ruleDep = new Deps.Dependency;
+		this.filterDep = new Deps.Dependency;
+		this.loadingDep = new Deps.Dependency;
+
+		// Autosubscribe to the record set published by the server based on the filter
+		// This will tear down server subscriptions when they are no longer being used.
+		this.sub = null;
+		this.comp = Deps.autorun(() => {
+			const rule = this.matchedRule();
+			const filter = this.getFilter();
+			if (this.sub) {
+				// Stop any existing sub immediately, don't wait
+				this.sub.stop();
+			}
+			if (!(rule && filter)) {
+				return;
+			}
+
+			// subscribe only for server-side collections
+			if (!isServerSearch(rule)) {
+				this.setLoaded(true);
+				return;
+			}
+			const params = getFindParams(rule, filter, this.limit);
+			const selector = params[0];
+			const options = params[1];
+
+			// console.debug 'Subscribing to <%s> in <%s>.<%s>', filter, rule.collection, rule.field
+			this.setLoaded(false);
+			const subName = rule.subscription || 'autocomplete-recordset';
+			this.sub = Meteor.subscribe(subName, selector, options, rule.collection, () => {
+				this.setLoaded(true);
+			});
+		});
+	}
+
+	teardown() {
+		// Stop the reactive computation we started for this autocomplete instance
+		this.comp.stop();
+	}
+
+	matchedRule() {
+		// reactive getters and setters for @filter and the currently matched rule
+		this.ruleDep.depend();
+		if (this.matched >= 0) {
+			return this.rules[this.matched];
+		} else {
+			return null;
+		}
+	}
+
+	setMatchedRule(i) {
+		this.matched = i;
+		this.ruleDep.changed();
+	}
+
+	getFilter() {
+		this.filterDep.depend();
+		return this.filter;
+	}
+
+	setFilter(x) {
+		this.filter = x;
+		this.filterDep.changed();
+		return this.filter;
+	}
+
+	isLoaded() {
+		this.loadingDep.depend();
+		return this.loaded;
+	}
+
+	setLoaded(val) {
+		if (val === this.loaded) {
+			return; //Don't cause redraws unnecessarily
+		}
+		this.loaded = val;
+		this.loadingDep.changed();
+	}
+
+	onKeyUp() {
+		if (!this.$element) {
+			return; //Don't try to do this while loading
+		}
+		const startpos = this.element.selectionStart;
+		const val = this.getText().substring(0, startpos);
+
+    /*
+      Matching on multiple expressions.
+      We always go from a matched state to an unmatched one
+      before going to a different matched one.
+     */
+		let i = 0;
+		let breakLoop = false;
+		while (i < this.expressions.length) {
+			const matches = val.match(this.expressions[i]);
+
+			// matching -> not matching
+			if (!matches && this.matched === i) {
+				this.setMatchedRule(-1);
+				breakLoop = true;
+			}
+
+			// not matching -> matching
+			if (matches && this.matched === -1) {
+				this.setMatchedRule(i);
+				breakLoop = true;
+			}
+
+			// Did filter change?
+			if (matches && this.filter !== matches[2]) {
+				this.setFilter(matches[2]);
+				breakLoop = true;
+			}
+			if (breakLoop) {
+				break;
+			}
+			i++;
+		}
+	}
+
+	onKeyDown(e) {
+		if (this.matched === -1 || (this.KEYS.indexOf(e.keyCode) < 0)) {
+			return;
+		}
+		switch (e.keyCode) {
+			case 9: //TAB
+			case 13: //ENTER
+				if (this.select()) { //Don't jump fields or submit if select successful
+					e.preventDefault();
+					e.stopPropagation();
+				}
+				break;
+				// preventDefault needed below to avoid moving cursor when selecting
+			case 40: //DOWN
+				e.preventDefault();
+				this.next();
+				break;
+			case 38: //UP
+				e.preventDefault();
+				this.prev();
+				break;
+			case 27: //ESCAPE
+				this.$element.blur();
+				this.hideList();
+		}
+	}
+
+	onFocus() {
+		// We need to run onKeyUp after the focus resolves,
+		// or the caret position (selectionStart) will not be correct
+		Meteor.defer(() => this.onKeyUp());
+	}
+
+	onBlur() {
+		// We need to delay this so click events work
+		// TODO this is a bit of a hack, see if we can't be smarter
+		Meteor.setTimeout(() => {
+			this.hideList();
+		}, 500);
+	}
+
+	onItemClick(doc) {
+		this.processSelection(doc, this.rules[this.matched]);
+	}
+
+	onItemHover(doc, e) {
+		this.tmplInst.$('.-autocomplete-item').removeClass('selected');
+		$(e.target).closest('.-autocomplete-item').addClass('selected');
+	}
+
+	filteredList() {
+		// @ruleDep.depend() # optional as long as we use depend on filter, because list will always get re-rendered
+		const filter = this.getFilter(); //Reactively depend on the filter
+		if (this.matched === -1) {
+			return null;
+		}
+		const rule = this.rules[this.matched];
+
+		// Don't display list unless we have a token or a filter (or both)
+		// Single field: nothing displayed until something is typed
+		if (!(rule.token || filter)) {
+			return null;
+		}
+		const params = getFindParams(rule, filter, this.limit);
+		const selector = params[0];
+		const options = params[1];
+		Meteor.defer(() => this.ensureSelection());
+
+		// if server collection, the server has already done the filtering work
+		if (isServerSearch(rule)) {
+			return AutoCompleteRecords.find({}, options);
+		}
+		// Otherwise, search on client
+		return rule.collection.find(selector, options);
+	}
+
+	isShowing() {
+		const rule = this.matchedRule();
+		// Same rules as above
+		const showing = rule && (rule.token || this.getFilter());
+
+		// Do this after the render
+		if (showing) {
+			Meteor.defer(() => {
+				this.positionContainer();
+				this.ensureSelection();
+			});
+		}
+		return showing;
+	}
+
+	// Replace text with currently selected item
+	select() {
+		const node = this.tmplInst.find('.-autocomplete-item.selected');
+		if (node == null) {
+			return false;
+		}
+		const doc = Blaze.getData(node);
+		if (!doc) {
+			return false; //Don't select if nothing matched
+
+		}
+		this.processSelection(doc, this.rules[this.matched]);
+		return true;
+	}
+
+	processSelection(doc, rule) {
+		const replacement = getField(doc, rule.field);
+		if (!isWholeField(rule)) {
+			this.replace(replacement, rule);
+			this.hideList();
+		} else {
+
+			// Empty string or doesn't exist?
+			// Single-field replacement: replace whole field
+			this.setText(replacement);
+
+			// Field retains focus, but list is hidden unless another key is pressed
+			// Must be deferred or onKeyUp will trigger and match again
+			// TODO this is a hack; see above
+			this.onBlur();
+		}
+		this.$element.trigger('autocompleteselect', doc);
+	}
+
+
+	// Replace the appropriate region
+	replace(replacement) {
+		const startpos = this.element.selectionStart;
+		const fullStuff = this.getText();
+		let val = fullStuff.substring(0, startpos);
+		val = val.replace(this.expressions[this.matched], `$1${ this.rules[this.matched].token }${ replacement }`);
+		const posfix = fullStuff.substring(startpos, fullStuff.length);
+		const separator = (posfix.match(/^\s/) ? '' : ' ');
+		const finalFight = val + separator + posfix;
+		this.setText(finalFight);
+		const newPosition = val.length + 1;
+		this.element.setSelectionRange(newPosition, newPosition);
+	}
+
+	hideList() {
+		this.setMatchedRule(-1);
+		this.setFilter(null);
+	}
+
+	getText() {
+		return this.$element.val() || this.$element.text();
+	}
+
+	setText(text) {
+		if (this.$element.is('input,textarea')) {
+			this.$element.val(text);
+		} else {
+			this.$element.html(text);
+		}
+	}
+
+
+  /*
+    Rendering functions
+   */
+
+	positionContainer() {
+		// First render; Pick the first item and set css whenever list gets shown
+		let pos;
+		const position = this.$element.position();
+		const rule = this.matchedRule();
+		const offset = getCaretCoordinates(this.element, this.element.selectionStart);
+
+		// In whole-field positioning, we don't move the container and make it the
+		// full width of the field.
+		if (rule && isWholeField(rule)) {
+			pos = {
+				left: position.left,
+				width: this.$element.outerWidth() //position.offsetWidth
+			};
+		} else { //Normal positioning, at token word
+			pos = { left: position.left + offset.left };
+		}
+
+		// Position menu from top (above) or from bottom of caret (below, default)
+		if (this.position === 'top') {
+			pos.bottom = this.$element.offsetParent().height() - position.top - offset.top;
+		} else {
+			pos.top = position.top + offset.top + parseInt(this.$element.css('font-size'));
+		}
+		this.tmplInst.$('.-autocomplete-container').css(pos);
+	}
+
+	ensureSelection() {
+		// Re-render; make sure selected item is something in the list or none if list empty
+		const selectedItem = this.tmplInst.$('.-autocomplete-item.selected');
+		if (!selectedItem.length) {
+			// Select anything
+			this.tmplInst.$('.-autocomplete-item:first-child').addClass('selected');
+		}
+	}
+
+	// Select next item in list
+	next() {
+		const currentItem = this.tmplInst.$('.-autocomplete-item.selected');
+		if (!currentItem.length) {
+			return;
+		}
+		currentItem.removeClass('selected');
+		const next = currentItem.next();
+		if (next.length) {
+			next.addClass('selected');
+		} else { //End of list or lost selection; Go back to first item
+			this.tmplInst.$('.-autocomplete-item:first-child').addClass('selected');
+		}
+	}
+
+	//Select previous item in list
+	prev() {
+		const currentItem = this.tmplInst.$('.-autocomplete-item.selected');
+		if (!currentItem.length) {
+			return; //Don't try to iterate an empty list
+		}
+		currentItem.removeClass('selected');
+		const prev = currentItem.prev();
+		if (prev.length) {
+			prev.addClass('selected');
+		} else { //Beginning of list or lost selection; Go to end of list
+			this.tmplInst.$('.-autocomplete-item:last-child').addClass('selected');
+		}
+	}
+
+	// This doesn't need to be reactive because list already changes reactively
+	// and will cause all of the items to re-render anyway
+	currentTemplate() {
+		return this.rules[this.matched].template;
+	}
+
+};
diff --git a/packages/meteor-autocomplete/autocomplete.css b/packages/meteor-autocomplete/client/autocomplete.css
similarity index 100%
rename from packages/meteor-autocomplete/autocomplete.css
rename to packages/meteor-autocomplete/client/autocomplete.css
diff --git a/packages/meteor-autocomplete/inputs.html b/packages/meteor-autocomplete/client/inputs.html
similarity index 100%
rename from packages/meteor-autocomplete/inputs.html
rename to packages/meteor-autocomplete/client/inputs.html
diff --git a/packages/meteor-autocomplete/client/templates.js b/packages/meteor-autocomplete/client/templates.js
new file mode 100755
index 0000000000000000000000000000000000000000..97b2e25971355c73ee7880e34c709a9d3746eeab
--- /dev/null
+++ b/packages/meteor-autocomplete/client/templates.js
@@ -0,0 +1,80 @@
+/* globals AutoComplete */
+//  Events on template instances, sent to the autocomplete class
+const acEvents = {
+	'keydown'(e, t) {
+		t.ac.onKeyDown(e);
+	},
+	'keyup'(e, t) {
+		t.ac.onKeyUp(e);
+	},
+	'focus'(e, t) {
+		t.ac.onFocus(e);
+	},
+	'blur'(e, t) {
+		t.ac.onBlur(e);
+	}
+};
+
+Template.inputAutocomplete.events(acEvents);
+
+Template.textareaAutocomplete.events(acEvents);
+
+const attributes = function() {
+	return _.omit(this, 'settings'); //Render all but the settings parameter
+
+};
+
+const autocompleteHelpers = {
+	attributes,
+	autocompleteContainer: new Template('AutocompleteContainer', function() {
+		const ac = new AutoComplete(Blaze.getData().settings);
+		// Set the autocomplete object on the parent template instance
+		this.parentView.templateInstance().ac = ac;
+
+		// Set nodes on render in the autocomplete class
+		this.onViewReady(function() {
+			ac.element = this.parentView.firstNode();
+			ac.$element = $(ac.element);
+		});
+		return Blaze.With(ac, function() { //eslint-disable-line
+			return Template._autocompleteContainer;
+		});
+	})
+};
+
+Template.inputAutocomplete.helpers(autocompleteHelpers);
+
+Template.textareaAutocomplete.helpers(autocompleteHelpers);
+
+Template._autocompleteContainer.rendered = function() {
+	this.data.tmplInst = this;
+};
+
+Template._autocompleteContainer.destroyed = function() {
+	// Meteor._debug "autocomplete destroyed"
+	this.data.teardown();
+};
+
+
+/*
+  List rendering helpers
+ */
+
+Template._autocompleteContainer.events({
+	// t.data is the AutoComplete instance; `this` is the data item
+	'click .-autocomplete-item'(e, t) {
+		t.data.onItemClick(this, e);
+	},
+	'mouseenter .-autocomplete-item'(e, t) {
+		t.data.onItemHover(this, e);
+	}
+});
+
+Template._autocompleteContainer.helpers({
+	empty() {
+		return this.filteredList().count() === 0;
+	},
+	noMatchTemplate() {
+		return this.matchedRule().noMatchTemplate || Template._noMatch;
+	}
+});
diff --git a/packages/meteor-autocomplete/package.js b/packages/meteor-autocomplete/package.js
index 5a6912af07a94fb162a3a78c14c36212a2c23423..ab887e0432afc1dffa6ef851c651759c87b11481 100755
--- a/packages/meteor-autocomplete/package.js
+++ b/packages/meteor-autocomplete/package.js
@@ -7,21 +7,21 @@ Package.describe({
 
 Package.onUse(function(api) {
 	api.use(['blaze', 'templating', 'jquery'], 'client');
-	api.use(['coffeescript', 'underscore', 'ecmascript']); // both
+	api.use(['underscore', 'ecmascript']); // both
 	api.use(['mongo', 'ddp']);
 
 	api.use('dandv:caret-position@2.1.0-3', 'client');
 
 	// Our files
 	api.addFiles([
-		'autocomplete.css',
-		'inputs.html',
-		'autocomplete-client.coffee',
-		'templates.coffee'
+		'client/autocomplete.css',
+		'client/inputs.html',
+		'client/autocomplete-client.js',
+		'client/templates.js'
 	], 'client');
 
 	api.addFiles([
-		'autocomplete-server.coffee'
+		'server/autocomplete-server.js'
 	], 'server');
 
 	api.export('Autocomplete', 'server');
@@ -31,7 +31,6 @@ Package.onUse(function(api) {
 Package.onTest(function(api) {
 	api.use('mizzao:autocomplete');
 
-	api.use('coffeescript');
 	api.use('mongo');
 	api.use('tinytest');
 
diff --git a/packages/meteor-autocomplete/server/autocomplete-server.js b/packages/meteor-autocomplete/server/autocomplete-server.js
new file mode 100755
index 0000000000000000000000000000000000000000..582daf40c1d1a1e6841f58e0a319a199a9b07a7a
--- /dev/null
+++ b/packages/meteor-autocomplete/server/autocomplete-server.js
@@ -0,0 +1,31 @@
+// This also attaches an onStop callback to sub, so we don't need to worry about that.
+// https://github.com/meteor/meteor/blob/devel/packages/mongo/collection.js
+const Autocomplete = class {
+	publishCursor(cursor, sub) {
+		Mongo.Collection._publishCursor(cursor, sub, 'autocompleteRecords');
+	}
+};
+
+Meteor.publish('autocomplete-recordset', function(selector, options, collName) {
+	const collection = global[collName];
+
+	// This is a semi-documented Meteor feature:
+	// https://github.com/meteor/meteor/blob/devel/packages/mongo-livedata/collection.js
+	if (!collection) {
+		throw new Error(`${ collName } is not defined on the global namespace of the server.`);
+	}
+	if (!collection._isInsecure()) {
+		Meteor._debug(`${ collName } is a secure collection, therefore no data was returned because the client could compromise security by subscribing to arbitrary server collections via the browser console. Please write your own publish function.`);
+		return []; // We need this for the subscription to be marked ready
+	}
+	if (options.limit) {
+		// guard against client-side DOS: hard limit to 50
+		options.limit = Math.min(50, Math.abs(options.limit));
+	}
+
+	// Push this into our own collection on the client so they don't interfere with other publications of the named collection.
+	// This also stops the observer automatically when the subscription is stopped.
+	Autocomplete.publishCursor(collection.find(selector, options), this);
+	// Mark the subscription ready after the initial addition of documents.
+	this.ready();
+});
diff --git a/packages/meteor-autocomplete/templates.coffee b/packages/meteor-autocomplete/templates.coffee
deleted file mode 100755
index cd56d8ba739846aa43fde4f8deca69d1eaf5dd63..0000000000000000000000000000000000000000
--- a/packages/meteor-autocomplete/templates.coffee
+++ /dev/null
@@ -1,50 +0,0 @@
-# Events on template instances, sent to the autocomplete class
-acEvents =
-  "keydown": (e, t) -> t.ac.onKeyDown(e)
-  "keyup": (e, t) -> t.ac.onKeyUp(e)
-  "focus": (e, t) -> t.ac.onFocus(e)
-  "blur": (e, t) -> t.ac.onBlur(e)
-
-Template.inputAutocomplete.events(acEvents)
-Template.textareaAutocomplete.events(acEvents)
-
-attributes = -> _.omit(@, 'settings') # Render all but the settings parameter
-
-autocompleteHelpers = {
-  attributes,
-  autocompleteContainer: new Template('AutocompleteContainer', ->
-    ac = new AutoComplete( Blaze.getData().settings )
-    # Set the autocomplete object on the parent template instance
-    this.parentView.templateInstance().ac = ac
-
-    # Set nodes on render in the autocomplete class
-    this.onViewReady ->
-      ac.element = this.parentView.firstNode()
-      ac.$element = $(ac.element)
-
-    return Blaze.With(ac, -> Template._autocompleteContainer)
-  )
-}
-
-Template.inputAutocomplete.helpers(autocompleteHelpers)
-Template.textareaAutocomplete.helpers(autocompleteHelpers)
-
-Template._autocompleteContainer.rendered = ->
-  @data.tmplInst = this
-
-Template._autocompleteContainer.destroyed = ->
-  # Meteor._debug "autocomplete destroyed"
-  @data.teardown()
-
-###
-  List rendering helpers
-###
-
-Template._autocompleteContainer.events
-  # t.data is the AutoComplete instance; `this` is the data item
-  "click .-autocomplete-item": (e, t) -> t.data.onItemClick(this, e)
-  "mouseenter .-autocomplete-item": (e, t) -> t.data.onItemHover(this, e)
-
-Template._autocompleteContainer.helpers
-  empty: -> @filteredList().count() is 0
-  noMatchTemplate: -> @matchedRule().noMatchTemplate || Template._noMatch
diff --git a/packages/rocketchat-api/server/v1/channels.js b/packages/rocketchat-api/server/v1/channels.js
index 1de2d82d724b238b812fdbb812f855044ddf9530..da10b21283d5f280aeb220152e2dcc8d6c36a3e5 100644
--- a/packages/rocketchat-api/server/v1/channels.js
+++ b/packages/rocketchat-api/server/v1/channels.js
@@ -128,7 +128,7 @@ RocketChat.API.v1.addRoute('channels.close', { authRequired: true }, {
 
 RocketChat.API.v1.addRoute('channels.create', { authRequired: true }, {
 	post() {
-		if (!RocketChat.authz.hasPermission(this.userId, 'create-p')) {
+		if (!RocketChat.authz.hasPermission(this.userId, 'create-c')) {
 			return RocketChat.API.v1.unauthorized();
 		}
 
diff --git a/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js b/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js
index daac0ea19932a009785e5e84f570348d34f8e558..e217c963e3f5f111c13cab42dc7f32609dfd3ecf 100644
--- a/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js
+++ b/packages/rocketchat-file-upload/server/config/configFileUploadAmazonS3.js
@@ -1,5 +1,5 @@
 /* globals Slingshot, FileUpload, AWS, SystemLogger */
-const crypto = Npm.require('crypto');
+import AWS4 from '../lib/AWS4.js';
 
 let S3accessKey;
 let S3secretKey;
@@ -9,11 +9,23 @@ const generateURL = function(file) {
 	if (!file || !file.s3) {
 		return;
 	}
-	const resourceURL = `/${ file.s3.bucket }/${ file.s3.path }${ file._id }`;
-	const expires = parseInt(new Date().getTime() / 1000) + Math.max(5, S3expiryTimeSpan);
-	const StringToSign = `GET\n\n\n${ expires }\n${ resourceURL }`;
-	const signature = crypto.createHmac('sha1', S3secretKey).update(new Buffer(StringToSign, 'utf-8')).digest('base64');
-	return `${ file.url }?AWSAccessKeyId=${ encodeURIComponent(S3accessKey) }&Expires=${ expires }&Signature=${ encodeURIComponent(signature) }`;
+
+	const credential = {
+		accessKeyId: S3accessKey,
+		secretKey: S3secretKey
+	};
+
+	const req = {
+		bucket: file.s3.bucket,
+		region: file.s3.region,
+		path: `/${ file.s3.path }${ file._id }`,
+		url: file.url,
+		expire: Math.max(5, S3expiryTimeSpan)
+	};
+
+	const queryString = AWS4.sign(req, credential);
+
+	return `${ file.url }?${ queryString }`;
 };
 
 FileUpload.addHandler('s3', {
diff --git a/packages/rocketchat-file-upload/server/lib/AWS4.js b/packages/rocketchat-file-upload/server/lib/AWS4.js
new file mode 100644
index 0000000000000000000000000000000000000000..bb8980ac0d4c79e8cdcb74e1007961e3f0810a1f
--- /dev/null
+++ b/packages/rocketchat-file-upload/server/lib/AWS4.js
@@ -0,0 +1,175 @@
+import crypto from 'crypto';
+import urllib from 'url';
+import querystring from 'querystring';
+
+const Algorithm = 'AWS4-HMAC-SHA256';
+const DefaultRegion = 'us-east-1';
+const Service = 's3';
+const KeyPartsRequest = 'aws4_request';
+
+class Aws4 {
+	constructor(req, credentials) {
+		const { url, method = 'GET', body = '', date, region, headers = {}, expire = 86400 } = this.req = req;
+
+		Object.assign(this, { url, body, method: method.toUpperCase() });
+
+		const urlObj = urllib.parse(url);
+		this.region = region || DefaultRegion;
+		this.path = urlObj.pathname;
+		this.host = urlObj.host;
+		this.date = date || this.amzDate;
+		this.credentials = credentials;
+		this.headers = this.prepareHeaders(headers);
+		this.expire = expire;
+	}
+
+	prepareHeaders() {
+		const host = this.host;
+
+		return {
+			host
+		};
+	}
+
+	hmac(key, string, encoding) {
+		return crypto.createHmac('sha256', key).update(string, 'utf8').digest(encoding);
+	}
+
+	hash(string, encoding = 'hex') {
+		return crypto.createHash('sha256').update(string, 'utf8').digest(encoding);
+	}
+
+	encodeRfc3986(urlEncodedString) {
+		return urlEncodedString.replace(/[!'()*]/g, function(c) {
+			return `%${ c.charCodeAt(0).toString(16).toUpperCase() }`;
+		});
+	}
+
+	encodeQuery(query) {
+		return this.encodeRfc3986(querystring.stringify(Object.keys(query).sort().reduce((obj, key) => {
+			if (!key) { return obj; }
+			obj[key] = !Array.isArray(query[key]) ? query[key] : query[key].slice().sort();
+			return obj;
+		}, {})));
+	}
+
+	get query() {
+		const query = {};
+
+		if (this.credentials.sessionToken) {
+			query['X-Amz-Security-Token'] = this.credentials.sessionToken;
+		}
+
+		query['X-Amz-Expires'] = this.expire;
+		query['X-Amz-Date'] = this.amzDate;
+		query['X-Amz-Algorithm'] = Algorithm;
+		query['X-Amz-Credential'] = `${ this.credentials.accessKeyId }/${ this.credentialScope }`;
+		query['X-Amz-SignedHeaders'] = this.signedHeaders;
+
+		return query;
+	}
+
+	get amzDate() {
+		return (new Date()).toISOString().replace(/[:\-]|\.\d{3}/g, '');
+	}
+
+	get dateStamp() {
+		return this.date.slice(0, 8);
+	}
+
+	get payloadHash() {
+		return 'UNSIGNED-PAYLOAD';
+	}
+
+	get canonicalPath() {
+		let pathStr = this.path;
+		if (pathStr === '/') { return pathStr; }
+
+		pathStr = pathStr.replace(/\/{2,}/g, '/');
+		pathStr = pathStr.split('/').reduce((path, piece) => {
+			if (piece === '..') {
+				path.pop();
+			} else {
+				path.push(this.encodeRfc3986(querystring.escape(piece)));
+			}
+			return path;
+		}, []).join('/');
+
+		return pathStr;
+	}
+
+	get canonicalQuery() {
+		return this.encodeQuery(this.query);
+	}
+
+	get canonicalHeaders() {
+		const headers = Object.keys(this.headers)
+			.sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : 1)
+			.map(key => `${ key.toLowerCase() }:${ this.headers[key] }`);
+		return `${ headers.join('\n') }\n`;
+	}
+
+	get signedHeaders() {
+		return Object.keys(this.headers)
+			.map(key => key.toLowerCase())
+			.sort()
+			.join(';');
+	}
+
+	get canonicalRequest() {
+		return [
+			this.method,
+			this.canonicalPath,
+			this.canonicalQuery,
+			this.canonicalHeaders,
+			this.signedHeaders,
+			this.payloadHash
+		].join('\n');
+	}
+
+	get credentialScope() {
+		return [
+			this.dateStamp,
+			this.region,
+			Service,
+			KeyPartsRequest
+		].join('/');
+	}
+
+	get stringToSign() {
+		return [
+			Algorithm,
+			this.date,
+			this.credentialScope,
+			this.hash(this.canonicalRequest)
+		].join('\n');
+	}
+
+	get signingKey() {
+		const kDate = this.hmac(`AWS4${ this.credentials.secretKey }`, this.dateStamp);
+		const kRegion = this.hmac(kDate, this.region);
+		const kService = this.hmac(kRegion, Service);
+		const kSigning = this.hmac(kService, KeyPartsRequest);
+
+		return kSigning;
+	}
+
+	get signature() {
+		return this.hmac(this.signingKey, this.stringToSign, 'hex');
+	}
+
+	// Export
+	// Return signed query string
+	sign() {
+		const query = this.query;
+		query['X-Amz-Signature'] = this.signature;
+
+		return this.encodeQuery(query);
+	}
+}
+
+export default {
+	sign(request, credential) {
+		return (new Aws4(request, credential)).sign();
+	}
+};
diff --git a/packages/rocketchat-file-upload/server/methods/getS3FileUrl.js b/packages/rocketchat-file-upload/server/methods/getS3FileUrl.js
index ef48ddc2163b815644a57dd9712f39e474327a69..c3cbca844eb80c638ad2f8cfcef6597f73065537 100644
--- a/packages/rocketchat-file-upload/server/methods/getS3FileUrl.js
+++ b/packages/rocketchat-file-upload/server/methods/getS3FileUrl.js
@@ -1,4 +1,5 @@
-const crypto = Npm.require('crypto');
+import AWS4 from '../lib/AWS4.js';
+
 let protectedFiles;
 let S3accessKey;
 let S3secretKey;
@@ -26,12 +27,22 @@ Meteor.methods({
 			throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'sendFileMessage' });
 		}
 		const file = RocketChat.models.Uploads.findOneById(fileId);
-		const resourceURL = `/${ file.s3.bucket }/${ file.s3.path }${ file._id }`;
-		const expires = parseInt(new Date().getTime() / 1000) + Math.max(5, S3expiryTimeSpan);
-		const StringToSign = `GET\n\n\n${ expires }\n${ resourceURL }`;
-		const signature = crypto.createHmac('sha1', S3secretKey).update(new Buffer(StringToSign, 'utf-8')).digest('base64');
-		return {
-			url:`${ file.url }?AWSAccessKeyId=${ encodeURIComponent(S3accessKey) }&Expires=${ expires }&Signature=${ encodeURIComponent(signature) }`
+
+		const credential = {
+			accessKeyId: S3accessKey,
+			secretKey: S3secretKey
+		};
+
+		const req = {
+			bucket: file.s3.bucket,
+			region: file.s3.region,
+			path: `/${ file.s3.path }${ file._id }`,
+			url: file.url,
+			expire: Math.max(5, S3expiryTimeSpan)
 		};
+
+		const queryString = AWS4.sign(req, credential);
+
+		return `${ file.url }?${ queryString }`;
 	}
 });
diff --git a/packages/rocketchat-google-natural-language/.npm/package/npm-shrinkwrap.json b/packages/rocketchat-google-natural-language/.npm/package/npm-shrinkwrap.json
index 0b44b80161cc02c6cbf5334e10d59e939220f616..e171621ad42f49f5aeb214b231cd0a9f40a36a58 100644
--- a/packages/rocketchat-google-natural-language/.npm/package/npm-shrinkwrap.json
+++ b/packages/rocketchat-google-natural-language/.npm/package/npm-shrinkwrap.json
@@ -1138,6 +1138,11 @@
       "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
       "from": "is-property@>=1.0.0 <2.0.0"
     },
+    "is-stream-ended": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.3.tgz",
+      "from": "is-stream-ended@>=0.1.0 <0.2.0"
+    },
     "is-typedarray": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
@@ -1408,21 +1413,9 @@
       "from": "sntp@>=1.0.0 <2.0.0"
     },
     "split-array-stream": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/split-array-stream/-/split-array-stream-1.0.2.tgz",
-      "from": "split-array-stream@>=1.0.0 <2.0.0",
-      "dependencies": {
-        "end-of-stream": {
-          "version": "1.4.0",
-          "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.0.tgz",
-          "from": "end-of-stream@>=1.4.0 <2.0.0"
-        },
-        "once": {
-          "version": "1.4.0",
-          "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
-          "from": "once@>=1.4.0 <2.0.0"
-        }
-      }
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/split-array-stream/-/split-array-stream-1.0.3.tgz",
+      "from": "split-array-stream@>=1.0.0 <2.0.0"
     },
     "sshpk": {
       "version": "1.13.0",
diff --git a/packages/rocketchat-i18n/i18n/ca.i18n.json b/packages/rocketchat-i18n/i18n/ca.i18n.json
index 436bcca1e9de01d37326825effe7f30fbfa4fe04..185c793f6340d4d2a15b4a30ace06b25ef5a0bda 100644
--- a/packages/rocketchat-i18n/i18n/ca.i18n.json
+++ b/packages/rocketchat-i18n/i18n/ca.i18n.json
@@ -17,7 +17,8 @@
   "Accessing_permissions": "L'accés als permisos",
   "Account_SID": "Compte SID",
   "Accounts": "Comptes",
-  "Accounts_AllowAnonymousAccess": "Permet accés anònim",
+  "Accounts_AllowAnonymousRead": "Permetre lectura anònima",
+  "Accounts_AllowAnonymousWrite": "Permetre escriptura anònima",
   "Accounts_AllowDeleteOwnAccount": "Permetre als usuaris eliminar el seu propi compte",
   "Accounts_AllowedDomainsList": "Llista de dominis permesos",
   "Accounts_AllowedDomainsList_Description": "Llista dels dominis permesos separada per comes ",
@@ -35,6 +36,7 @@
   "Accounts_BlockedUsernameList": "Llista de noms d'usuari bloquejats",
   "Accounts_BlockedUsernameList_Description": "Llista  separada per comes de noms d'usuari bloquejats (no distingeix majúscules/minúscules)",
   "Accounts_CustomFields_Description": "Ha de ser un objecte JSON vàlid on les claus són els noms dels camps i contenen un diccionari amb les opcions del camp. Exemple:<br/><code>{\n \"role\": {\n  \"type\": \"select\",\n  \"defaultValue\": \"student\",\n  \"options\": [\"teacher\", \"student\"],\n  \"required\": true,\n  \"modifyRecordField\": {\n   \"array\": true,\n   \"field\": \"roles\"\n  }\n },\n \"twitter\": {\n  \"type\": \"text\",\n  \"required\": true,\n  \"minLength\": 2,\n  \"maxLength\": 10\n }\n}</code> ",
+  "Accounts_DefaultUsernamePrefixSuggestion": "Prefix suggerit per al nom d'usuari per defecte",
   "Accounts_denyUnverifiedEmail": "Denegar correu electrònic sense verificar",
   "Accounts_EmailVerification": "Verificació de correu electrònic",
   "Accounts_EmailVerification_Description": "Assegura't que la configuració SMTP és correcta per fer servir aquesta funcionalitat",
@@ -265,6 +267,7 @@
   "BotHelpers_userFields": "Camps d'usuari",
   "BotHelpers_userFields_Description": "CSV de camps d'usuari que poden ser accedits pels mètodes helper dels bots.",
   "Branch": "Branca",
+  "Broadcast_Connected_Instances": "Difusió de les instàncies connectades",
   "Bugsnag_api_key": "Clau API Bugsnag",
   "busy": "ocupat",
   "Busy": "Ocupat",
@@ -360,6 +363,7 @@
   "CROWD_Reject_Unauthorized": "Rebutja no autoritzat",
   "CRM_Integration": "Integració CRM",
   "Current_Chats": "Xats actuals",
+  "Current_Status": "Estat actual",
   "Custom": "Personalitzat",
   "Custom_Emoji": "Emoticona personalitzada",
   "Custom_Emoji_Add": "Afegir nova emoticona",
@@ -668,11 +672,11 @@
   "Iframe_Integration_receive_enable": "Activa recepció",
   "Iframe_Integration_receive_enable_Description": "Permetre que la finestra pare enviï ordres a Rocket.Chat.",
   "Iframe_Integration_receive_origin": "Rebre orígens",
-  "Iframe_Integration_receive_origin_Description": "Només les pàgines de les quals es proporciona l'origen podran enviar ordres o `*` per a totes. Es poden utilitzar múltiples valors separats per `,`. Exemple `http://localhost,https://localhost`",
+  "Iframe_Integration_receive_origin_Description": "Origens amb prefix del protocol, separats per comes, dels quals es permet rebre comandes. Exemple 'http://localhost, https://localhost', o * per permetre rebre de qualsevol lloc.",
   "Iframe_Integration_send_enable": "Activa enviament",
   "Iframe_Integration_send_enable_Description": "Envia esdeveniments a la finestra pare",
   "Iframe_Integration_send_target_origin": "Envia l'origen a l'objectiu",
-  "Iframe_Integration_send_target_origin_Description": "Només les pàgines amb l'origen proporcionat podran rebre esdeveniments o `*` per a totes.  Exemple `http://localhost`",
+  "Iframe_Integration_send_target_origin_Description": "Origens amb prefix del protocol on les comandes són enviades. Exemple 'https://localhost', o * per permetre enviar a qualsevol lloc.",
   "Importer_Archived": "Arxivat",
   "Importer_CSV_Information": "L'importador CSV requereix un format específic, si us plau llegiu la documentació sobre com estructurar l'arxiu .zip:",
   "Importer_HipChatEnterprise_Information": "L'arxiu pujat ha de ser un tar.gz desencriptat. Si us plau, llegiu la documentació per a més informació:",
@@ -704,6 +708,7 @@
   "Install_FxOs_follow_instructions": "Si us plau, confirma la instal·lació de l'aplicació al teu dispositiu (polsi \"Instal·lar\" quan se us demani).",
   "Installation": "Instal·lació",
   "Installed_at": "Instal·lat a",
+  "Instance_Record": "Registre d'instància",
   "Instructions_to_your_visitor_fill_the_form_to_send_a_message": "Instruccions als visitants, ompliu el formulari per enviar un missatge",
   "Impersonate_user": "Suplantar usuari",
   "Impersonate_user_description": "Quan s'activa, la integració publica com a l'usuari que ha desencadenat la integració",
@@ -1124,6 +1129,7 @@
   "or": "o",
   "Open_your_authentication_app_and_enter_the_code": "Obre l'app d'autenticació i entra el codi. També pots utilitzar un dels codis de recuperació.",
   "Order": "Ordre",
+  "Or_talk_as_anonymous": "O parla com a anònim",
   "OS_Arch": "Arquitectura del sistema",
   "OS_Cpus": "Recompte de CPU",
   "OS_Freemem": "Memòria RAM lliure",
@@ -1228,7 +1234,6 @@
   "Register": "Crea un compte nou",
   "Registration": "Registre",
   "Registration_Succeeded": "Registre reeixit",
-  "Register_or_login_to_send_messages": "Registra't o identifica't per enviar missatges",
   "Registration_via_Admin": "Registre via Admin",
   "Regular_Expressions": "Expressions regulars",
   "Release": "Llançament",
@@ -1254,6 +1259,7 @@
   "Reset_password": "Reinicialitza la contrasenya",
   "Restart": "Reinicia (restart)",
   "Restart_the_server": "Reinicia el servidor",
+  "Retry_Count": "Comptador de reintents",
   "Role": "Rol",
   "Role_Editing": "Edició de rols",
   "Role_removed": "Rol eliminat",
@@ -1363,6 +1369,7 @@
   "Showing_archived_results": "<p>Mostrant <b>%s</b> resultats arxivats</p>",
   "Showing_online_users": "Mostrant-ne <b>__total_showing__</b>, En línia: __online__, Total: __total__ usuaris",
   "Showing_results": "<p>Mostrant <b>%s</b> resultats</p>",
+  "Sign_in_to_start_talking": "Identifica't per començar a parlar",
   "since_creation": "des de %s",
   "Site_Name": "Nom del lloc",
   "Site_Url": "URL del lloc",
@@ -1557,6 +1564,7 @@
   "Unread_Rooms": "Sales no llegides",
   "Unread_Rooms_Mode": "Mode de sales no llegides",
   "Unstar_Message": "Esborra el destacat",
+  "Updated_at": "Actualitzat el",
   "Upload_file_description": "Descripció de l'arxiu",
   "Upload_file_name": "Nom de l'arxiu",
   "Upload_file_question": "Pujar l'arxiu?",
diff --git a/packages/rocketchat-i18n/i18n/cs.i18n.json b/packages/rocketchat-i18n/i18n/cs.i18n.json
index ba2dab094d9aaf1329a074484b881bde67931f71..f3d89cf4c104f0353fcbbc415f1047e455fd3b2f 100644
--- a/packages/rocketchat-i18n/i18n/cs.i18n.json
+++ b/packages/rocketchat-i18n/i18n/cs.i18n.json
@@ -17,7 +17,8 @@
   "Accessing_permissions": "Přístup k oprávnění",
   "Account_SID": "SID účtu",
   "Accounts": "Účty",
-  "Accounts_AllowAnonymousAccess": "Povolit anonymní přístup",
+  "Accounts_AllowAnonymousRead": "Povolit anonymům číst",
+  "Accounts_AllowAnonymousWrite": "Povolit anonymům zapisovat",
   "Accounts_AllowDeleteOwnAccount": "Povolit uživatelům odstranit vlastní účet",
   "Accounts_AllowedDomainsList": "Seznam povolených domén",
   "Accounts_AllowedDomainsList_Description": "Čárkami oddělený seznam povolených domén",
@@ -35,6 +36,7 @@
   "Accounts_BlockedUsernameList": "Zakázaná uživatelská jména",
   "Accounts_BlockedUsernameList_Description": "čárkou oddělený seznam uživatelských jmen (na velikosti písmen nezáleží)",
   "Accounts_CustomFields_Description": "Validní JSON obsahující klíče polí s nastavením. Například:<br/><code>{\n \"role\": {\n  \"type\": \"select\",\n  \"defaultValue\": \"student\",\n  \"options\": [\"teacher\", \"student\"],\n  \"required\": true,\n  \"modifyRecordField\": {\n   \"array\": true,\n   \"field\": \"roles\"\n  }\n },\n \"twitter\": {\n  \"type\": \"text\",\n  \"required\": true,\n  \"minLength\": 2,\n  \"maxLength\": 10\n }\n}</code>",
+  "Accounts_DefaultUsernamePrefixSuggestion": "Výchozí návrh prefixu uživatelského jména",
   "Accounts_denyUnverifiedEmail": "Zakázat neověřené e-mailové adresy",
   "Accounts_EmailVerification": "Ověření e-mailu",
   "Accounts_EmailVerification_Description": "Pro použití této funkce se ujistěte, že máte správné nastavení SMTP",
@@ -265,6 +267,7 @@
   "BotHelpers_userFields": "Uživatelská pole",
   "BotHelpers_userFields_Description": "CSV uživatelských polí, která budou přístupná botům",
   "Branch": "Větev",
+  "Broadcast_Connected_Instances": "Připojené instance",
   "Bugsnag_api_key": "Bugsnag API klíč",
   "busy": "zaneprázdněný",
   "Busy": "Zaneprázdněný",
@@ -360,6 +363,7 @@
   "CROWD_Reject_Unauthorized": "Zamítnout neutorizované",
   "CRM_Integration": "Integrace CRM",
   "Current_Chats": "Aktuální Místnosti",
+  "Current_Status": "Aktuální stav",
   "Custom": "Vlastní",
   "Custom_Emoji": "Vlastní emotikona",
   "Custom_Emoji_Add": "Přidat novou emotikonu",
@@ -704,6 +708,7 @@
   "Install_FxOs_follow_instructions": "Prosím potvrďte instalaci aplikace na Vašem přístroji (stiskněte tlačítko \"Install\" po výzvě).",
   "Installation": "Instalace",
   "Installed_at": "instalováno v",
+  "Instance_Record": "ID Instance",
   "Instructions_to_your_visitor_fill_the_form_to_send_a_message": "Pokyny pro Vaše návštěvníky k vyplnění formulář pro odeslání zprávy",
   "Impersonate_user": "Vydávat se za uživatele",
   "Impersonate_user_description": "Pokud je povoleno, integrace posílá za uživatele, který ji vyvolal",
@@ -1124,6 +1129,7 @@
   "or": "nebo",
   "Open_your_authentication_app_and_enter_the_code": "Otevřete autentizační aplikaci a zadejte vygenerovaný kód. Můžete použít jeden ze svých záložních kódů.",
   "Order": "Objednat",
+  "Or_talk_as_anonymous": "Vydávat sezaanonyma",
   "OS_Arch": "Architektura OS",
   "OS_Cpus": "Počet CPU OS",
   "OS_Freemem": "Volná paměť OS",
@@ -1228,7 +1234,6 @@
   "Register": "Zaregistrovat nový účet",
   "Registration": "Registrace",
   "Registration_Succeeded": "Registrace úspěšná",
-  "Register_or_login_to_send_messages": "Pro odeslání zpráv je třeba se zaregistrovat nebo přihlásit",
   "Registration_via_Admin": "Registrace přes Admin",
   "Regular_Expressions": "Regulární výrazy",
   "Release": "Verze",
@@ -1254,6 +1259,7 @@
   "Reset_password": "Obnovit heslo",
   "Restart": "Restartovat",
   "Restart_the_server": "Restartovat server",
+  "Retry_Count": "Počet opakování",
   "Role": "Role",
   "Role_Editing": "Editace Role",
   "Role_removed": "Role odstraněna",
@@ -1363,6 +1369,7 @@
   "Showing_archived_results": "<p> <b>Zobrazeno %s</b> archivovaných výsledků </p>",
   "Showing_online_users": "Viditelných <b>__total_showing__</b> z __total__ uživatelů",
   "Showing_results": "<p>Zobrazeno <b>%s</b> výsledků</p>",
+  "Sign_in_to_start_talking": "Pro konverzaci se přihlašte",
   "since_creation": "od %s",
   "Site_Name": "Jméno stránky",
   "Site_Url": "URL stránky",
@@ -1557,6 +1564,7 @@
   "Unread_Rooms": "Nepřečtené místnosti",
   "Unread_Rooms_Mode": "Mód Nepřečtených místností",
   "Unstar_Message": "Odebrat hvězdičku",
+  "Updated_at": "Poslední aktualizace",
   "Upload_file_description": "Popis souboru",
   "Upload_file_name": "Název souboru",
   "Upload_file_question": "Nahrát soubor?",
diff --git a/packages/rocketchat-i18n/i18n/de-AT.i18n.json b/packages/rocketchat-i18n/i18n/de-AT.i18n.json
index 95bf6f0116731ae1a8db1a1a98390008a1ecc203..f89e6d847474833a6b4cd6f284e7fe623caa73e1 100644
--- a/packages/rocketchat-i18n/i18n/de-AT.i18n.json
+++ b/packages/rocketchat-i18n/i18n/de-AT.i18n.json
@@ -55,6 +55,7 @@
   "Accounts_OAuth_Custom_id": "ID",
   "Accounts_OAuth_Custom_Identity_Path": "Identitätspfad",
   "Accounts_OAuth_Custom_Login_Style": "Anmeldungsart",
+  "Accounts_OAuth_Custom_Merge_Users": "BenutzerInnen zusammenführen",
   "Accounts_OAuth_Custom_Scope": "Bereich",
   "Accounts_OAuth_Custom_Secret": "Secret",
   "Accounts_OAuth_Custom_Token_Path": "Pfad des Token",
@@ -118,12 +119,12 @@
   "Add_agent": "Agent hinzufügen",
   "Add_custom_oauth": "Benutzerdefiniertes OAuth-Konto hinzufügen",
   "Add_manager": "Manager hinzufügen",
-  "Add_user": "Benutzer hinzufügen",
-  "Add_User": "Benutzer hinzufügen",
-  "Add_users": "Benutzer hinzufügen",
+  "Add_user": "BenutzerIn hinzufügen",
+  "Add_User": "BenutzerIn hinzufügen",
+  "Add_users": "BenutzerInnen hinzufügen",
   "Adding_OAuth_Services": "Hinzufügen von OAuth-Services",
   "Adding_permission": "Berechtigung hinzufügen",
-  "Adding_user": "Benutzer hinzufügen",
+  "Adding_user": "Füge BenutzerIn hinzu",
   "Additional_emails": "Zusätzliche E-Mails",
   "Additional_Feedback": "Zusätzliches Feedback",
   "Administration": "Administration",
@@ -205,6 +206,7 @@
   "Back_to_integrations": "Zurück zu Integrationen",
   "Back_to_login": "Zurück zum Login",
   "Back_to_permissions": "Zurück zu den Berechtigungen",
+  "Block_User": "BenutzerIn sperren",
   "Body": "Body",
   "bold": "fett",
   "Branch": "Branch",
@@ -328,7 +330,7 @@
   "Edit_Department": "Abteilung bearbeiten",
   "edited": "bearbeitet",
   "Editing_room": "Raum bearbeiten",
-  "Editing_user": "Benutzer bearbeiten",
+  "Editing_user": "Benutzern bearbeiten",
   "Email": "E-Mail",
   "Email_address_to_send_offline_messages": "E-Mail-Adresse zum Senden von Offline-Nachrichten",
   "Email_already_exists": "Die E-Mail-Adresse existiert bereits.",
@@ -461,6 +463,7 @@
   "Force_SSL": "SSL erzwingen",
   "Force_SSL_Description": "*Achtung!* _Force SSL_ solte niemals mit einem Reverse-Proxy verwendet werden. Falls Sie einen Reverse-Proxy verwenden, sollten Sie die Weiterleitung DORT einrichten. Dies Option existiert für Anwendungen wie Heroku, die keine Weiterleitungskonfigurationen für Reverse-Proxy erlauben.",
   "Forgot_password": "Passwort vergessen?",
+  "Forward_to_user": "An BenutzerIn weiterleiten",
   "Frequently_Used": "Häufig verwendet",
   "From": "Absender",
   "From_Email": "Absender",
@@ -523,6 +526,9 @@
   "Integration_added": "Die Integration wurde hinzugefügt.",
   "Integration_Incoming_WebHook": "Eingehende WebHook-Integration",
   "Integration_New": "Neue Integration",
+  "Integrations_Outgoing_Type_RoomJoined": "BenutzerIn hat den Raum betreten",
+  "Integrations_Outgoing_Type_RoomLeft": "BenutzerIn hat den Raum verlassen",
+  "Integrations_Outgoing_Type_UserCreated": "BenutzerIn angelegt.",
   "Integration_Outgoing_WebHook": "Ausgehende WebHook-Integration",
   "Integration_Word_Trigger_Placement_Description": "Soll das auslösende Wort irgendwo im Satz stehen und nicht nur am Anfang? ",
   "Integration_updated": "Die Integration wurde aktualisiert.\n",
@@ -548,7 +554,7 @@
   "Invitation_Subject": "Einladungsbetreff",
   "Invitation_Subject_Default": "Sie wurden zu [Site_Name] eingeladen",
   "Invite_user_to_join_channel": "Benutzer in diesen Raum einladen",
-  "Invite_Users": "Benutzer einladen",
+  "Invite_Users": "BenutzerInnen einladen",
   "is_also_typing": "schreibt auch",
   "is_also_typing_female": "schreibt auch",
   "is_also_typing_male": "schreibt auch",
diff --git a/packages/rocketchat-i18n/i18n/de.i18n.json b/packages/rocketchat-i18n/i18n/de.i18n.json
index 24e7b29e401dcc777e1c19727e05da759fc595ea..8503b587ac362ad28d73b6c7bd0a3d3d56e267d9 100644
--- a/packages/rocketchat-i18n/i18n/de.i18n.json
+++ b/packages/rocketchat-i18n/i18n/de.i18n.json
@@ -17,6 +17,8 @@
   "Accessing_permissions": "Zugriff auf Berechtigungen",
   "Account_SID": "Konto-SID",
   "Accounts": "Konten",
+  "Accounts_AllowAnonymousRead": "Erlaube Anonymes lesen",
+  "Accounts_AllowAnonymousWrite": "Erlaube Anonymes schreiben",
   "Accounts_AllowDeleteOwnAccount": "Benutzern erlauben, ihr Konto zu löschen",
   "Accounts_AllowedDomainsList": "Liste von erlaubten Domains",
   "Accounts_AllowedDomainsList_Description": "Durch Kommata getrennte Liste von erlaubten Domains",
@@ -103,6 +105,8 @@
   "Accounts_OAuth_Wordpress_id": "WordPress-ID",
   "Accounts_OAuth_Wordpress_secret": "Geheimer WordPress Schlüssel",
   "Accounts_PasswordReset": "Passwort zurücksetzen",
+  "Accounts_OAuth_Proxy_host": "Proxy Host",
+  "Accounts_OAuth_Proxy_services": "Proxy Service",
   "Accounts_Registration_AuthenticationServices_Default_Roles": "Standardrolle für Authentifizierungsdienste",
   "Accounts_Registration_AuthenticationServices_Default_Roles_Description": "Standardrollen die Benutzern zugewiesen werden, die sich über Authentifizierungsdienste registrieren",
   "Accounts_Registration_AuthenticationServices_Enabled": "Anmeldung mit Authentifizierungsdiensten",
@@ -130,12 +134,12 @@
   "Add_custom_oauth": "Benutzerdefiniertes OAuth-Konto hinzufügen",
   "Add_Domain": "Domain hinzufügen",
   "Add_manager": "Manager hinzufügen",
-  "Add_user": "Benutzer hinzufügen",
-  "Add_User": "Benutzer hinzufügen",
-  "Add_users": "Benutzer hinzufügen",
+  "Add_user": "BenutzerIn hinzufügen",
+  "Add_User": "BenutzerIn hinzufügen",
+  "Add_users": "BenutzerInnen hinzufügen",
   "Adding_OAuth_Services": "Hinzufügen von OAuth-Services",
   "Adding_permission": "Berechtigung hinzufügen",
-  "Adding_user": "Benutzer hinzufügen",
+  "Adding_user": "Füge BenutzerIn hinzu",
   "Additional_emails": "Zusätzliche E-Mails",
   "Additional_Feedback": "Zusätzliches Feedback",
   "Administration": "Administration",
@@ -175,6 +179,7 @@
   "API_Drupal_URL_Description": "Beispiel: https://domain.de (ohne schließenden /)",
   "API_Embed": "Einbetten",
   "API_Embed_Description": "Eingebettete Link Vorschau für Links die von Benutzern gepostet wurden aktiv.",
+  "API_EmbedCacheExpirationDays": "Tage bis zum Ablauf den eingebetteten Caches",
   "API_EmbedDisabledFor": "Einbettungen für Benutzer deaktivieren",
   "API_EmbedDisabledFor_Description": "Durch Kommata getrennte Liste von Benutzernamen",
   "API_EmbedIgnoredHosts": "Ignorierte Hosts einbetten",
@@ -182,9 +187,11 @@
   "API_EmbedSafePorts": "Sichere Ports",
   "API_EmbedSafePorts_Description": "Kommagetrennte Liste der Ports, für die eine Vorschau erlaubt ist.",
   "API_Enable_CORS": "Aktiviere CORS",
+  "API_Enable_Direct_Message_History_EndPoint": "Aktiviere den Endpunkt  für den Verlauf von Direkt Nachrichten",
   "API_GitHub_Enterprise_URL": "Server-URL",
   "API_GitHub_Enterprise_URL_Description": "Beispiel: http://domain.com (ohne Schrägstrich am Ende)",
   "API_Gitlab_URL": "GitLab-URL",
+  "API_Shield_Types": "Shield Typ",
   "API_Token": "API-Token",
   "API_Upper_Count_Limit": "Max. Anzahl an Einträgen",
   "API_Upper_Count_Limit_Description": "Max. Anzahl an Einträgen die die REST API zurückliefen soll (sofern ohne Einschränkung)? ",
@@ -205,6 +212,7 @@
   "Assign_admin": "Admin zuweisen",
   "at": "am",
   "Attachment_File_Uploaded": "Datei hochgeladen",
+  "Attribute_handling": "Behandlung von Attributen",
   "Auth_Token": "Auth-Token",
   "Author": "Autor",
   "Authorization_URL": "Autorisierungs-URL",
@@ -237,8 +245,10 @@
   "Back": "Zurück",
   "Back_to_applications": "Zurück zu den Anwendungen",
   "Back_to_integrations": "Zurück zu Integrationen",
+  "Back_to_integration_detail": "Zurück zu den Integrations Details",
   "Back_to_login": "Zurück zum Login",
   "Back_to_permissions": "Zurück zu den Berechtigungen",
+  "Block_User": "BenutzerIn sperren",
   "Body": "Body",
   "bold": "fett",
   "bot_request": "Bot-Anfrage",
@@ -257,10 +267,13 @@
   "Cancel": "Abbrechen",
   "Cancel_message_input": "Abbrechen",
   "Cannot_invite_users_to_direct_rooms": "Benutzer können nicht in private Nachrichtenräume eingeladen werden.",
+  "CAS_autoclose": "Login Pupp automatisch schließen",
+  "CAS_base_url_Description": "Haupt URL des externen Singe Sign On Services e.g: https://sso.example.undef/sso/",
   "CAS_button_color": "Hintergrundfarbe des Login-Buttons",
   "CAS_button_label_color": "Farbe des Login-Button-Texts",
   "CAS_button_label_text": "Text des Login-Buttons",
   "CAS_enabled": "Aktiviert",
+  "CAS_login_url_Description": "Login URL des externen Singe Sign On Services e.g: https://sso.example.undef/sso/login",
   "CAS_popup_height": "Höhe des Login Pop Up",
   "CAS_popup_width": "Breite des Login Pop Up",
   "CAS_Sync_User_Data_Enabled": "Benutzerdaten immer synchronisieren",
@@ -291,8 +304,10 @@
   "Choose_the_username_that_this_integration_will_post_as": "Wählen Sie den Benutzernamen, der die Integration veröffentlicht.",
   "clear": "löschen",
   "clear_cache_now": "Zwischenspeicher jetzt leeren",
+  "clear_history": "Verlauf löschen",
   "Clear_all_unreads_question": "Möchten Sie alle ungelesenen Nachrichten löschen?",
   "Click_here": "Hier klicken",
+  "Click_here_for_more_info": "Hier klicken für weitere Informationen",
   "Client_ID": "Client-ID",
   "Client_Secret": "Client-Schlüssel",
   "Clients_will_refresh_in_a_few_seconds": "Clients werden in wenigen Sekunden aktualisiert",
@@ -336,15 +351,23 @@
   "Custom_Fields": "Benutzerdefinierte Felder",
   "Custom_oauth_helper": "Bei der Einrichtung muss eine Rückruf-URL angegeben werden. Benutze dafür folgende URL: <pre>%s</pre>",
   "Custom_oauth_unique_name": "Name des OAuth-Kontos",
+  "Custom_Scripts": "Benutzerdefinierte Skripte",
   "Custom_Script_Logged_In": "Benutzerdefiniertes Script für angemeldete Benutzer",
   "Custom_Script_Logged_Out": "Benutzerdefiniertes Script für abgemeldete Benutzer",
+  "Custom_Sounds": "Benutzerdefinierte Töne",
+  "Custom_Sound_Add": "Benutzerdefinierte Töne hinzufügen",
+  "Custom_Sound_Delete_Warning": "Ein gelöschter Ton kann nicht wiederhergestellt werden.",
+  "Custom_Sound_Error_Invalid_Sound": "Fehlerhafter Ton",
   "Custom_Translations": "Benutzerdefinierte Ãœbersetzungen",
   "Dashboard": "Dashboard",
   "Date": "Datum",
+  "Date_From": "von",
+  "Date_to": "bis",
   "days": "Tage",
   "DB_Migration": "Datenbankmigration",
   "DB_Migration_Date": "Datenbankmigrationsdatum",
   "Deactivate": "Deaktivieren",
+  "Decline": "ablehnen",
   "Default": "Voreinstellung",
   "Delete": "Löschen",
   "Delete_message": "Nachricht löschen",
@@ -366,6 +389,8 @@
   "Desktop_Notifications_Enabled": "Desktop-Benachrichtigungen sind aktiviert.",
   "Direct_message_someone": "Jemandem eine private Nachricht schicken",
   "Direct_Messages": "Private Nachrichten",
+  "Disable_Notifications": "Benachrichtigungen deaktivieren",
+  "Disable_two-factor_authentication": "2 Faktor Authentifizierung deaktivieren",
   "Display_offline_form": "Offline Nachricht anzeigen",
   "Displays_action_text": "Zeigt Aktionstext",
   "Do_you_want_to_change_to_s_question": "Möchten Sie dies zu <strong>%s</strong> ändern?",
@@ -384,7 +409,7 @@
   "Edit_Department": "Abteilung bearbeiten",
   "edited": "bearbeitet",
   "Editing_room": "Raum bearbeiten",
-  "Editing_user": "Benutzer bearbeiten",
+  "Editing_user": "BenutzerIn bearbeiten",
   "Email": "E-Mail",
   "Email_address_to_send_offline_messages": "E-Mail-Adresse zum Senden von Offline-Nachrichten",
   "Email_already_exists": "Die E-Mail-Adresse existiert bereits.",
@@ -403,7 +428,9 @@
   "Empty_title": "Es wurde kein Titel angegeben.",
   "Enable": "Aktivieren",
   "Enable_Desktop_Notifications": "Aktivieren",
+  "Enable_two-factor_authentication": "2-Faktor Authentifizierung aktivieren",
   "Enabled": "Aktiviert",
+  "Enable_Svg_Favicon": "SVG Favicon aktivieren",
   "Encrypted_message": "Verschlüsselte Nachricht",
   "End_OTR": "OTR beenden",
   "Enter_a_regex": "Regex eingeben",
@@ -437,9 +464,11 @@
   "error-invalid-channel-start-with-chars": "Ungültiger Kanal. Beginnen Sie mit @ oder #",
   "error-invalid-custom-field": "Ungültiges benutzerdefiniertes Feld",
   "error-invalid-custom-field-name": "Unzulässiger Name für ein benutzerdefiniertes Feld. Benutze nur Buchstaben, Nummern, Binde- und Unterstriche.",
+  "error-invalid-date": "Das eingegebene Datum ist fehlerhaft.",
   "error-invalid-description": "Ungültige Beschreibung",
   "error-invalid-domain": "Ungültige Domain",
   "error-invalid-email": "Ungültige E-Mail-Adresse: __email__",
+  "error-invalid-email-address": "Fehlerhafte E-Mail-Adresse",
   "error-invalid-file-height": "Ungültige Dateihöhe",
   "error-invalid-file-type": "Ungültiges Dateiformat",
   "error-direct-message-file-upload-not-allowed": "Dateiaustausch ist in direkten Nachrichten nicht möglich.",
@@ -499,8 +528,10 @@
   "File_exceeds_allowed_size_of_bytes": "Die Datei ist größer als das erlaubte Maximum von __size__ Bytes",
   "File_not_allowed_direct_messages": "Dateiaustausch ist in direkten Nachrichten nicht möglich.",
   "File_type_is_not_accepted": "Feldtyp nicht akzeptiert.",
+  "File_uploaded": "Datei hochladen",
   "FileUpload": "Dateien hochladen",
   "FileUpload_Enabled": "Hochladen von Dateien aktivieren",
+  "FileUpload_Disabled": "Datei Uploads ",
   "FileUpload_Enabled_Direct": "Dateiaustausch ist in direkten Nachrichten möglich.",
   "FileUpload_File_Empty": "Datei ist leer",
   "FileUpload_FileSystemPath": "Systempfad",
@@ -524,17 +555,22 @@
   "Follow_social_profiles": "Folge uns in sozialen Netzwerken, fork uns auf GitHub und teile deine Gedanken über die Rocket.Chat-App auf unserem Trello-Board.",
   "Food_and_Drink": "Essen & Trinken",
   "Footer": "Fußzeile",
+  "Fonts": "Schriften",
   "For_your_security_you_must_enter_your_current_password_to_continue": "Geben Sie zu Ihrer Sicherheit Ihr aktuelles Passwort ein um fortzufahren.",
   "Force_SSL": "SSL erzwingen",
   "Force_SSL_Description": "*Achtung!* _Force SSL_ solte niemals mit einem Reverse-Proxy verwendet werden. Falls Sie einen Reverse-Proxy verwenden, sollten Sie die Weiterleitung DORT einrichten. Dies Option existiert für Anwendungen wie Heroku, die keine Weiterleitungskonfigurationen für Reverse-Proxy erlauben.",
   "Forgot_password": "Passwort vergessen?",
+  "Forgot_Password_Email_Subject": "[Site_Name] - Passwort Wiederherstellung",
+  "Forgot_Password_Email": "<a href=\"[Forgot_Password_Url]\">Hier</a> Klicken um das Passwort zurückzusetzen.",
+  "Forgot_password_section": "Passwort vergessen",
   "Forward": "Weiterleiten",
   "Forward_chat": "Chat weiterleiten",
   "Forward_to_department": "An Abteilung weiterleiten",
-  "Forward_to_user": "An Benutzer weiterleiten",
+  "Forward_to_user": "An BenutzerIn weiterleiten",
   "Frequently_Used": "Häufig verwendet",
+  "Friday": "Freitag",
   "From": "Absender",
-  "From_Email": "Absender",
+  "From_Email": "E-Mail Absender",
   "From_email_warning": "<b>Warnung</b>: Der <b>Absender</b> ist Gegenstand deiner Mail-Server-Einstellungen.",
   "General": "Allgemeines",
   "github_no_public_email": "Sie haben keine öffentliche E-Mail-Adresse in Ihrem GitHub-Account.",
@@ -545,6 +581,7 @@
   "Guest_Pool": "Gästepool",
   "Hash": "Hash",
   "Header": "Kopfzeile",
+  "Header_and_Footer": "Kopf und Fusszeile",
   "Hidden": "Versteckt",
   "Hide_Avatars": "Avatar verstecken",
   "Hide_flextab": "Rechte Seitenleiste über Klick verstecken",
@@ -552,6 +589,7 @@
   "Hide_Private_Warning": "Sind sie sicher, das Gespräch mit \"%s\" zu verstecken?",
   "Hide_room": "Raum verstecken",
   "Hide_Room_Warning": "Sind sie sicher, den Raum \"%s\" zu verstecken?",
+  "Hide_roles": "Rollen verstecken",
   "Hide_usernames": "Benutzernamen ausblenden",
   "Highlights": "Hervorhebungen",
   "Highlights_How_To": "Um benachrichtigt zu werden, wenn ein Wort oder Ausdruck erwähnt wird, fügen Sie ihn hier hinzu. Sie können Wörter und Ausdrücke mit Kommata trennen. Die Wörter zur Hervorhebung beachten die Groß- und Kleinschreibung nicht.",
@@ -598,9 +636,17 @@
   "Installation": "Installation",
   "Installed_at": "Installationsdatum",
   "Instructions_to_your_visitor_fill_the_form_to_send_a_message": "Anweisungen an Ihre Besucher: Füllen Sie das Formular aus, um eine Nachricht zu senden.",
+  "Integration_Advanced_Settings": "Erweiterte Einstellungen",
   "Integration_added": "Die Integration wurde hinzugefügt.",
   "Integration_Incoming_WebHook": "Eingehende WebHook-Integration",
   "Integration_New": "Neue Integration",
+  "Integrations_Outgoing_Type_FileUploaded": "Hochgeladene Dateien",
+  "Integrations_Outgoing_Type_RoomArchived": "Archivierte Räume",
+  "Integrations_Outgoing_Type_RoomCreated": "Raum erstellt (öffentlich und privat)",
+  "Integrations_Outgoing_Type_RoomJoined": "BenutzerIn hat den Raum betreten",
+  "Integrations_Outgoing_Type_RoomLeft": "BenutzerIn hat den Raum verlassen",
+  "Integrations_Outgoing_Type_SendMessage": "Nachricht gesendet",
+  "Integrations_Outgoing_Type_UserCreated": "BenutzerIn angelegt.",
   "Integration_Outgoing_WebHook": "Ausgehende WebHook-Integration",
   "Integration_Word_Trigger_Placement_Description": "Soll das auslösende Wort irgendwo im Satz stehen und nicht nur am Anfang? ",
   "Integration_updated": "Die Integration wurde aktualisiert.\n",
@@ -618,15 +664,17 @@
   "Invalid_pass": "Es muss ein Passwort angegeben werden.",
   "Invalid_room_name": "<strong>%s</strong> ist kein zulässiger Raumname.<br/> Verwenden Sie  nur Buchstaben, Zahlen oder Binde- und Unterstriche.",
   "Invalid_secret_URL_message": "Die angegebene URL ist ungültig.",
+  "Invalid_two_factor_code": "Fehlerhafter 2-Faktor Code",
   "invisible": "unsichtbar",
   "Invisible": "Unsichtbar",
+  "Invitation": "Einladung",
   "Invitation_HTML": "Einladungstext (HTML)",
   "Invitation_HTML_Default": "<h2> Sie wurden eingeladen zu <h1> [Site_Name] </h1></h2><p> Besuchen Sie zu [Site_URL] und probieren Sie heute die beste verfügbare Open-Source-Chat-Lösung aus! </p>",
   "Invitation_HTML_Description": "Sie können die folgenden Platzhalter verwenden: <br /><ul><li> [email] für den Empfänger der E-Mail. </li><li> [Site_Name] und [Site_URL] jeweils für den Anwendungsnamen und die URL. </li></ul>",
   "Invitation_Subject": "Einladungsbetreff",
   "Invitation_Subject_Default": "Sie wurden zu [Site_Name] eingeladen",
-  "Invite_user_to_join_channel": "Benutzer in diesen Kanal einladen",
-  "Invite_Users": "Benutzer einladen",
+  "Invite_user_to_join_channel": "BenutzerIn in diesen Kanal einladen",
+  "Invite_Users": "BenutzerInnen einladen",
   "is_also_typing": "schreibt auch",
   "is_also_typing_female": "schreibt auch",
   "is_also_typing_male": "schreibt auch",
@@ -639,6 +687,7 @@
   "Jitsi_Enable_Channels": "Aktivieren in Kanälen",
   "join": "Beitreten",
   "Join_audio_call": "Anruf beitreiten",
+  "Join_Chat": "Chat beitreten",
   "Join_default_channels": "Standardkanälen beitreten",
   "Join_the_Community": "Trete der Community bei",
   "Join_the_given_channel": "Diesem Kanal beitreten",
@@ -718,6 +767,7 @@
   "Leave_Private_Warning": "Sind sie sicher, das Gespräch mit \"%s\" zu verlassen?",
   "Leave_room": "Raum verlassen",
   "Leave_Room_Warning": "Sind sie sicher, den Raum \"%s\" zu verlassen?",
+  "Leave_the_current_channel": "Aktuellen Kanal verlassen",
   "line": "Zeilen",
   "List_of_Channels": "Liste der Kanäle",
   "List_of_Direct_Messages": "Liste der Direktnachrichten",
@@ -810,7 +860,9 @@
   "Message_ShowFormattingTips": "Formatierungstipps anzeigen",
   "Message_starring": "Markieren von Nachrichten",
   "Message_TimeFormat": "Zeitformat",
+  "Message_TimeAndDateFormat": "Zeit und Datumsformat",
   "Message_TimeFormat_Description": "Siehe auch: <a href=\"http://momentjs.com/docs/#/displaying/format/\" target=\"momemt\">Moment.js</a>",
+  "Message_TimeAndDateFormat_Description": "Siehe auch: <a href=\"http://momentjs.com/docs/#/displaying/format/\" target=\"momemt\">Moment.js</a>",
   "Message_too_long": "Diese Nachricht ist zu lang.",
   "Message_VideoRecorderEnabled": "Videoaufnahmen eingeschaltet",
   "Message_VideoRecorderEnabledDescription": "Videoformat auf webm beim \"Datei hochladen\" einschränken? (Video abspielen funktioniert dann in fast allen Browsern)",
@@ -818,11 +870,14 @@
   "Messages_that_are_sent_to_the_Incoming_WebHook_will_be_posted_here": "Nachrichten, die an den eingehenden Webhook gesendet werden, werden hier veröffentlicht.",
   "Meta": "Metadaten",
   "Meta_fb_app_id": "Facebook-App-ID",
+  "Meta_custom": "Benutzerdefinierte Meta Tags",
   "Meta_google-site-verification": "Google-Seiten-Verifizierung",
   "Meta_language": "Sprache",
   "Meta_msvalidate01": "MSValidate.01",
   "Meta_robots": "Roboter",
+  "Min_length_is": "Die minimale länge beträgt %s",
   "minutes": "Minuten",
+  "Mobile": "Mobil",
   "Monday": "Montag",
   "Monitor_history_for_changes_on": "Verlaufsänderungen beobachten für",
   "More_channels": "Mehr Kanäle",
@@ -886,16 +941,19 @@
   "Off_the_record_conversation_is_not_available_for_your_browser_or_device": "Off-the-record-Gespräche sind für Ihren Browser oder Ihr Gerät nicht verfügbar.",
   "Office_Hours": "Bürozeiten",
   "Office_hours_enabled": "Bürozeiten aktiviert",
+  "Office_hours_updated": "Bürozeiten aktualisiert",
   "Offline": "Offline",
   "Offline_DM_Email": "Sie haben eine private Nachricht von __user__ erhalten.",
   "Offline_form": "Offline-Formular",
   "Offline_form_unavailable_message": "Nachricht, dass Offline Formular ungültig",
+  "Offline_Link_Message": "gehe zur Nachricht",
   "Offline_Mention_Email": "Sie wurden von __user__ in #__room__ erwähnt.",
   "Offline_message": "Offline-Nachricht",
   "Offline_success_message": "Nachricht, dass Offline Nachricht erfolgreich",
   "Offline_unavailable": "offline - nicht verfügbar",
   "On": "Ein",
   "Online": "Online",
+  "Only_On_Desktop": "Desktop Modus (senden mit der \"Enter\" Taste nur auf dem Desktop PC)",
   "Only_you_can_see_this_message": "Nur Sie können diese Nachricht sehen.",
   "Oops!": "Hoppla",
   "Open": "Öffnen",
@@ -941,10 +999,12 @@
   "Please_enter_value_for_url": "Bitte geben Sie eine URL für Ihr Profilbild ein.",
   "Please_enter_your_new_password_below": "Bitte geben Sie Ihr neues Passwort ein:",
   "Please_enter_your_password": "Bitte Passwort eingeben",
+  "please_enter_valid_domain": "Bitte eine valide Domain eingeben",
   "Please_fill_a_label": "Bitte Bezeichnung ausfüllen",
   "Please_fill_a_name": "Bitte geben Sie einen Namen ein.",
   "Please_fill_a_username": "Bitte geben Sie einen Benutzernamen ein.",
   "Please_fill_name_and_email": "Bitte geben Sie einen Namen und eine E-Mail-Adresse ein.",
+  "Please_select_an_user": "Bitte einen Benutzer auswählen",
   "Please_select_enabled_yes_or_no": "Bitte wählen Sie eine Option für \"aktiviert\".",
   "Please_wait": "Bitte warten",
   "Please_wait_activation": "Bitte warten, der Vorgang kann einige Zeit in Anspruch nehmen.",
@@ -988,14 +1048,23 @@
   "quote": "Zitat",
   "Quote": "Zitieren",
   "Random": "Zufällig",
+  "React_when_read_only": "Reaktionen erlauben",
   "Reacted_with": "Reagierte mit",
   "Reactions": "Reaktionen",
+  "Read_only": "Nur lesend",
+  "Read_only_channel": "Kanal nur lesbar",
+  "Read_only_group": "Nur lesbar Gruppe",
   "Record": "Aufnehmen",
   "Redirect_URI": "Weiterleitungs-URL",
+  "Refresh_oauth_services": "oAuth Service aktualisieren",
   "Refresh_keys": "Schlüssel aktualisieren",
   "Refresh_your_page_after_install_to_enable_screen_sharing": "Aktualisieren Sie die Seite nach der Installation, um die Bildschirmübertragung zu aktivieren.",
+  "Regenerate_codes": "Codes neu generieren",
   "Register": "Neues Konto registrieren",
+  "Registration": "Registrierung",
   "Registration_Succeeded": "Ihre Registrierung war erfolgreich.",
+  "Registration_via_Admin": "Registrierung via Admin",
+  "Regular_Expressions": "Reguläre Ausdrücke",
   "Release": "Veröffentlichung",
   "Remove": "Entfernen",
   "Remove_Admin": "Admin entfernen",
@@ -1003,8 +1072,10 @@
   "Remove_as_owner": "als Besitzer entfernen",
   "Remove_custom_oauth": "OAuth-Konto entfernen",
   "Remove_from_room": "Aus dem Raum entfernen",
+  "Remove_last_admin": "Entferne den letzen Admin",
   "Remove_someone_from_room": "Jemanden aus dem Raum entfernen",
   "Removed": "Entfernt",
+  "Reply": "Antwort",
   "Report_Abuse": "Missbrauch melden",
   "Report_exclamation_mark": "Melden!",
   "Report_sent": "Bericht gesendet",
@@ -1028,10 +1099,16 @@
   "room_changed_topic": "Das Thema des Raums wurde von <em>__user_by__</em> zu <em>__room_topic__</em> geändert.",
   "Room_description_changed_successfully": "Raumbeschreibung erfolgreich geändert",
   "Room_has_been_deleted": "Der Raum wurde gelöscht.",
+  "Room_has_been_archived": "Der Raum wurde archiviert.",
+  "Room_has_been_unarchived": "Der Raum wurde dearchiviert.",
   "Room_Info": "Raum",
+  "room_is_blocked": "Der räum ist geblockt",
+  "room_is_read_only": "Der Raum ist nur lesbar",
+  "room_name": "Raum Name",
   "Room_name_changed": "<em>__user_by__</em> hat den Raumnamen zu  <em>__room_name__</em> geändert.",
   "Room_name_changed_successfully": "Der Raumname wurde erfolgreich geändert.",
   "Room_not_found": "Der Raum konnte nicht gefunden werden.",
+  "Room_password_changed_successfully": "Das Raum Passwort wurde erfolgreich geändert",
   "Room_topic_changed_successfully": "Das Thema des Raums wurde erfolgreich geändert.",
   "Room_type_changed_successfully": "Der Raumtyp wurde erfolgreich geändert.",
   "Room_unarchived": "Der Raum wurde wiederhergestellt.",
@@ -1046,6 +1123,8 @@
   "SAML_Custom_Generate_Username": "Benutzernamen generieren",
   "SAML_Custom_Issuer": "Benutzerdefinierter Aussteller",
   "SAML_Custom_Provider": "Benutzerdefinierter Provider",
+  "SAML_Custom_Public_Cert": "Öffentliches Zertifikat",
+  "SAML_Custom_Private_Key": "Privater Schlüssel",
   "Saturday": "Samstag",
   "Save": "Speichern",
   "Save_changes": "Änderungen speichern",
@@ -1099,6 +1178,7 @@
   "Show_all": "Alle Nutzer zeigen",
   "Show_more": "Mehr Nutzer zeigen",
   "show_offline_users": "Zeige Benutzer an, die offline sind",
+  "Show_on_registration_page": "Auf der Registrierungsseite anzeigen",
   "Show_only_online": "Nur Online-Nutzer zeigen",
   "Show_preregistration_form": "Vorregistrierungsformular zeigen",
   "Showing_archived_results": "<b>%s</b> archivierte Räume",
@@ -1117,6 +1197,7 @@
   "Slash_Topic_Description": "Thema setzen",
   "Slash_Topic_Params": "Themennachricht",
   "Smarsh_Enabled": "Smarsh aktiviert",
+  "Smarsh_MissingEmail_Email": "Fehlende E-Mail",
   "Smileys_and_People": "Gesichter & Personen",
   "SMS_Enabled": "SMS aktiviert",
   "SMTP": "SMTP",
@@ -1125,6 +1206,7 @@
   "SMTP_Port": "SMTP-Port",
   "SMTP_Test_Button": "SMTP-Einstellungen testen",
   "SMTP_Username": "SMTP-Benutzername",
+  "Snippet_Added": "Erstellt am %s",
   "Sound": "Ton",
   "SSL": "SSL",
   "Star_Message": "Nachricht markieren",
@@ -1165,7 +1247,9 @@
   "Symbols": "Symbole",
   "Sync_success": "Die Synchronisierung war erfolgreich.",
   "Sync_Users": "Benutzer synchronisieren",
+  "System_messages": "System Nachrichten",
   "Tag": "Tag",
+  "TargetRoom": "Ziel Raum!",
   "Test_Connection": "Testverbindung",
   "Test_Desktop_Notifications": "Desktop-Benachrichtigungen testen",
   "Thank_you_exclamation_mark": "Vielen Dank!",
@@ -1200,11 +1284,16 @@
   "There_are_no_agents_added_to_this_department_yet": "Es wurden bisher keine Agenten zu dieser Abteilung hinzugefügt.",
   "There_are_no_integrations": "Es sind keine Integrationen vorhanden.",
   "There_are_no_users_in_this_role": "Es sind dieser Rolle keine Benutzer zugeordnet.",
+  "This_conversation_is_already_closed": "Die Unterhaltung wurde bereits beendet.",
   "This_email_has_already_been_used_and_has_not_been_verified__Please_change_your_password": "Diese E-Mail wurde bereits verschickt, aber noch nicht bestätigt. Bitte ändern Sie Ihr Passwort.",
   "This_is_a_desktop_notification": "Das ist eine Desktop-Benachrichtigung.",
   "This_is_a_push_test_messsage": "Dies ist eine Test-Push-Nachricht.",
   "This_room_has_been_archived_by__username_": "Dieser Raum wurde von __username__ archiviert",
   "This_room_has_been_unarchived_by__username_": "Dieser Raum wurde von __username__ unarchiviert",
+  "Two-factor_authentication": "2-Faktor Authentifizierung",
+  "Two-factor_authentication_disabled": "2-Faktor Authentifizierung deaktiviert",
+  "Two-factor_authentication_enabled": "2-Faktor Authentifizierung aktiviert",
+  "Two-factor_authentication_is_currently_disabled": "2-Faktor Authentifizierung ist momentan deaktiviert",
   "Thursday": "Donnerstag",
   "Time_in_seconds": "Zeit in Sekunden",
   "Title": "Titel",
@@ -1216,6 +1305,8 @@
   "To_users": "An die Benutzer",
   "Topic": "Thema",
   "Travel_and_Places": "Reisen & Orte",
+  "Translated": "übersetzt",
+  "Translations": "Ãœbersetzungen",
   "Trigger_removed": "Auslöser entfernt",
   "Trigger_Words": "Trigger Words",
   "Triggers": "Auslöser",
@@ -1228,7 +1319,9 @@
   "Type_your_new_password": "Geben Sie Ihr neues Passwort ein",
   "UI_DisplayRoles": "Rollen anzeigen",
   "UI_Merge_Channels_Groups": "Führe private Gruppen und Kanäle zusammen",
+  "UI_Use_Real_Name": "Benutze den Realen Namen",
   "Unarchive": "Wiederherstellen",
+  "Unblock_User": "Benutzer entsperren",
   "Unmute_someone_in_room": "Jemanden das Chatten in einem Raum wieder erlauben",
   "Unmute_user": "Benutzern das Chatten erlauben ",
   "Unnamed": "Unbenannt",
@@ -1273,6 +1366,7 @@
   "User_left_male": "Der Benutzer <em>__user_left__</em> hat den Kanal verlassen.",
   "User_logged_out": "Der Benutzer wurde abgemeldet.",
   "User_management": "Benutzerverwaltung",
+  "User_muted": "Benutzer stummgeschaltet",
   "User_muted_by": "Dem Benutzer <em>__user_muted__</em> wurde das Chatten von <em>__user_by__</em> verboten.",
   "User_not_found": "Der Benutzer konnte nicht gefunden werden.",
   "User_not_found_or_incorrect_password": "Entweder konnte der Benutzer nicht gefunden werden oder Sie haben ein falsches Passwort angegeben.",
@@ -1297,15 +1391,23 @@
   "Username_title": "Benutzernamen festlegen",
   "Username_wants_to_start_otr_Do_you_want_to_accept": "__username__ möchte ein OTR-Gespräch starten. Möchten Sie annehmen?",
   "Users": "Benutzer",
+  "Users_added": "Die Benutzer wurden hinzugefügt",
   "Users_in_role": "Zugeordnete Nutzer",
   "UTF8_Names_Slugify": "UTF8-Namen-Slugify",
   "UTF8_Names_Validation": "UTF8-Namen-Verifizierung",
   "UTF8_Names_Validation_Description": "Erlauben Sie keine Sonderzeichen und Leerzeichen. Sie können - _ und . verwenden, aber nicht am Ende eines Namens.",
+  "Validate_email_address": "E-Mail-Adresse bestätigen",
+  "Verification": "Überprüfung ",
   "Verification_email_sent": "Bestätigungsmail gesendet",
+  "Verification_Email_Subject": "[Site_Name] - Bestätige dein Benutzerkonto",
+  "Verification_Email": "Klicke <a href=\"[Verification_Url]\">hier</a> um dein Benutzerkonto zu bestätigen.",
   "Verified": "Verifiziert",
+  "Verify": "überprüfen",
   "Version": "Version",
   "Video_Chat_Window": "Video-Chat",
+  "Video_Conference": "Video-Konferenz",
   "Videocall_declined": "Videoanruf abgelehnt.",
+  "Videocall_enabled": "Videoanruf aktiviert",
   "View_All": "Alle ansehen",
   "View_Logs": "Logs anzeigen",
   "View_mode": "Ansichts-Modus",
@@ -1319,6 +1421,7 @@
   "Visitor_page_URL": "URL der Besucherseite",
   "Visitor_time_on_site": "Besucherzeit auf der Seite",
   "Wait_activation_warning": "Bevor Sie sich anmelden können, muss das Konto von einem Administrator manuell aktiviert werden.",
+  "Warnings": "Warnungen",
   "We_are_offline_Sorry_for_the_inconvenience": "Wir sind offline. Entschuldigen Sie die Unannehmlichkeiten.",
   "We_have_sent_password_email": "Wir haben Ihnen eine Anleitung zum Zurücksetzen des Passworts an Ihre E-Mail-Adresse gesendet. Wenn Sie keine E-Mail erhalten haben, versuchen Sie es bitte noch einmal.",
   "We_have_sent_registration_email": "Wir haben Ihnen eine Bestätigungsmail gesendet. Wenn Sie keine E-Mail erhalten haben, versuchen Sie es bitte noch einmal.",
diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json
index 1d18d31ef92382a2ac30d5dd485734ed7c9739f6..0be9c641d9cff4742e049ab1dceed31fc3ec5c9f 100644
--- a/packages/rocketchat-i18n/i18n/en.i18n.json
+++ b/packages/rocketchat-i18n/i18n/en.i18n.json
@@ -267,6 +267,7 @@
   "BotHelpers_userFields": "User Fields",
   "BotHelpers_userFields_Description": "CSV of user fields that can be accessed by bots helper methods.",
   "Branch": "Branch",
+  "Broadcast_Connected_Instances": "Broadcast Connected Instances",
   "Bugsnag_api_key": "Bugsnag API Key",
   "busy": "busy",
   "Busy": "Busy",
@@ -362,6 +363,7 @@
   "CROWD_Reject_Unauthorized": "Reject Unauthorized",
   "CRM_Integration": "CRM Integration",
   "Current_Chats": "Current Chats",
+  "Current_Status": "Current Status",
   "Custom": "Custom",
   "Custom_Emoji": "Custom Emoji",
   "Custom_Emoji_Add": "Add New Emoji",
@@ -706,6 +708,7 @@
   "Install_FxOs_follow_instructions": "Please confirm the app installation on your device (press \"Install\" when prompted).",
   "Installation": "Installation",
   "Installed_at": "Installed at",
+  "Instance_Record": "Instance Record",
   "Instructions_to_your_visitor_fill_the_form_to_send_a_message": "Instructions to your visitor fill the form to send a message",
   "Impersonate_user": "Impersonate User",
   "Impersonate_user_description": "When enabled, integration posts as the user that triggered integration",
@@ -1256,6 +1259,7 @@
   "Reset_password": "Reset password",
   "Restart": "Restart",
   "Restart_the_server": "Restart the server",
+  "Retry_Count": "Retry Count",
   "Role": "Role",
   "Role_Editing": "Role Editing",
   "Role_removed": "Role removed",
@@ -1560,6 +1564,7 @@
   "Unread_Rooms": "Unread Rooms",
   "Unread_Rooms_Mode": "Unread Rooms Mode",
   "Unstar_Message": "Remove Star",
+  "Updated_at": "Updated at",
   "Upload_file_description": "File description",
   "Upload_file_name": "File name",
   "Upload_file_question": "Upload file?",
diff --git a/packages/rocketchat-i18n/i18n/fr.i18n.json b/packages/rocketchat-i18n/i18n/fr.i18n.json
index 7d9b43e0c9fcb5533db0a5d4bffa9cd8a4ecb787..96b29850cf853a08c020201eca7c637cd2d21fce 100644
--- a/packages/rocketchat-i18n/i18n/fr.i18n.json
+++ b/packages/rocketchat-i18n/i18n/fr.i18n.json
@@ -10,13 +10,15 @@
   "__username__is_no_longer__role__defined_by__user_by_": "__user_by__ a retiré le rôle __role__ à __username__",
   "__username__was_set__role__by__user_by_": "__user_by__ a donné le rôle __role__ à __username__",
   "Accept": "Accepter",
-  "Accept_incoming_livechat_requests_even_if_there_are_no_online_agents": "Accepter les demande de chat en direct même si il n'y a pas d'assistant en ligne",
-  "Accept_with_no_online_agents": "Accepter sans assistant enligne",
+  "Accept_incoming_livechat_requests_even_if_there_are_no_online_agents": "Accepter les demandes de chat en ligne même si il n'y a pas d'agent en ligne",
+  "Accept_with_no_online_agents": "Accepter sans agent en ligne",
   "Access_not_authorized": "Accès non autorisé",
   "Access_Token_URL": "URL du jeton d'accès",
   "Accessing_permissions": "Accès aux permissions",
   "Account_SID": "SID du compte",
   "Accounts": "Comptes",
+  "Accounts_AllowAnonymousRead": "Autoriser la lecture anonyme",
+  "Accounts_AllowAnonymousWrite": "Autoriser l'écriture anonyme",
   "Accounts_AllowDeleteOwnAccount": "Autoriser les utilisateurs à supprimer leur propre compte",
   "Accounts_AllowedDomainsList": "Liste des domaines autorisés",
   "Accounts_AllowedDomainsList_Description": "Liste des domaines autorisés, séparés par des virgules",
@@ -34,6 +36,7 @@
   "Accounts_BlockedUsernameList": "Liste des noms d'utilisateurs bloqués",
   "Accounts_BlockedUsernameList_Description": "Liste de noms d'utilisateurs bloqués (insensible à la casse), séparés par des virgules",
   "Accounts_CustomFields_Description": "Devrait être un JSON valide où les clés sont les noms des champs contenant un dictionnaire de champs de paramétrage. Exemple :<br/>\n<code>{\n \"role\": {\n  \"type\": \"select\",\n  \"defaultValue\": \"eleve\",\n  \"options\": [\"enseignant\", \"eleve\"],\n  \"required\": true,\n  \"modifyRecordField\": {\n   \"array\": true,\n   \"field\": \"roles\"\n  }\n },\n \"twitter\": {\n  \"type\": \"text\",\n  \"required\": true,\n  \"minLength\": 2,\n  \"maxLength\": 10\n }\n}</code> ",
+  "Accounts_DefaultUsernamePrefixSuggestion": "Suggestion par défaut du préfixe du nom d'utilisateur",
   "Accounts_denyUnverifiedEmail": "Refuser les e-mails non vérifiés",
   "Accounts_EmailVerification": "Vérification de l'adresse e-mail",
   "Accounts_EmailVerification_Description": "Vous devez avoir des paramètres SMTP corrects pour utiliser cette fonctionnalité",
@@ -137,8 +140,8 @@
   "Additional_Feedback": "Commentaires supplémentaires",
   "Administration": "Administration",
   "After_OAuth2_authentication_users_will_be_redirected_to_this_URL": "Après l'authentification par OAuth2, les utilisateurs seront redirigés vers cette URL",
-  "Agent": "Assistant",
-  "Agent_added": "Assistant ajouté",
+  "Agent": "Agent",
+  "Agent_added": "Agent ajouté",
   "Agent_removed": "Assistant supprimé",
   "Alias": "Alias",
   "Alias_Format": "Format d'alias",
@@ -377,8 +380,8 @@
   "Delete_Room_Warning": "Supprimer un salon supprimera également tous les messages postés dans le salon. Cette action est irréversible.",
   "Delete_User_Warning": "Supprimer un utilisateur va également supprimer tous les messages de celui-ci. Cette action est irréversible.",
   "Deleted": "Supprimé !",
-  "Department": "Département",
-  "Department_removed": "Département supprimé",
+  "Department": "Service",
+  "Department_removed": "Service supprimé",
   "Departments": "Départements",
   "Deployment_ID": "ID de déploiement",
   "Description": "Description",
@@ -409,7 +412,7 @@
   "Duration": "Durée",
   "Edit": "Modifier",
   "Edit_Custom_Field": "Modifier le champ personnalisé",
-  "Edit_Department": "Éditer le département",
+  "Edit_Department": "Éditer le service",
   "Edit_Trigger": "Éditer le déclencheur",
   "edited": "modifié",
   "Editing_room": "Modification du salon",
@@ -459,7 +462,7 @@
   "error-could-not-change-name": "Impossible de modifier le nom",
   "error-could-not-change-username": "Impossible de modifier le nom d'utilisateur",
   "error-delete-protected-role": "Impossible de supprimer un rôle protégé",
-  "error-department-not-found": "Département introuvable",
+  "error-department-not-found": "Service introuvable",
   "error-duplicate-channel-name": "Un canal avec le nom '__channel_name__' existe déjà",
   "error-email-domain-blacklisted": "Le domaine de l'adresse e-mail est sur liste noire",
   "error-email-send-failed": "Erreur lors de la tentative d'envoi d'e-mail : __message__",
@@ -581,7 +584,7 @@
   "Forgot_password_section": "Mot de passe oublié",
   "Forward": "Transmettre",
   "Forward_chat": "Transmettre la conversation",
-  "Forward_to_department": "Transmettre au département",
+  "Forward_to_department": "Transmettre au service",
   "Forward_to_user": "Transmettre à l'utilisateur",
   "Frequently_Used": "Fréquemment utilisé",
   "Friday": "Vendredi",
@@ -614,10 +617,10 @@
   "Host": "Hôte",
   "hours": "heures",
   "Hours": "Heures",
-  "How_friendly_was_the_chat_agent": "L'assistant du chat était-il amical ?",
-  "How_knowledgeable_was_the_chat_agent": "L'assistant du chat était-il clair ?",
+  "How_friendly_was_the_chat_agent": "Votre interlocuteur était-il amical ?",
+  "How_knowledgeable_was_the_chat_agent": "Votre interlocuteur était-il clair ?",
   "How_long_to_wait_after_agent_goes_offline": "Délai d'attente après que l'agent soit hors ligne",
-  "How_responsive_was_the_chat_agent": "L'assistant du chat avait-il des réponses adaptées ?",
+  "How_responsive_was_the_chat_agent": "Votre interlocuteur avait-il des réponses adaptées ?",
   "How_satisfied_were_you_with_this_chat": "Étiez-vous satisfait de ce chat?",
   "How_to_handle_open_sessions_when_agent_goes_offline": "Comment gérer les sessions ouvertes lorsque l'asistant passe hors ligne",
   "If_this_email_is_registered": "Si cet e-mail est enregistré, les instructions pour réinitialiser votre mot de passe vous serons envoyées. Si vous ne recevez pas d'email rapidement, merci de revenir et d'essayer à nouveau.",
@@ -953,12 +956,12 @@
   "N_new_messages": "%s nouveaux messages",
   "Name": "Nom",
   "Name_cant_be_empty": "Le nom ne peut pas être vide",
-  "Name_of_agent": "Nom de l'assistant",
+  "Name_of_agent": "Nom de l'agent",
   "Name_optional": "Nom (optionnel)",
   "Navigation_History": "Historique de navigation",
   "New_Application": "Nouvelle application",
   "New_Custom_Field": "Nouveau champ personnalisé",
-  "New_Department": "Nouveau département",
+  "New_Department": "Nouveau service",
   "New_integration": "Nouvelle intégration",
   "New_logs": "Nouveaux journaux",
   "New_Message_Notification": "Notification de nouveau message",
@@ -968,7 +971,7 @@
   "New_Room_Notification": "Notification de nouveau salon",
   "New_videocall_request": "Nouvelle demande d'appel vidéo",
   "New_Trigger": "Nouveau déclencheur",
-  "No_available_agents_to_transfer": "Aucun assistant disponible pour le transfert",
+  "No_available_agents_to_transfer": "Aucun agent disponible pour le transfert",
   "No_channel_with_name_%s_was_found": "Aucun canal nommé <strong>\"%s\"</strong> n'a été trouvé !",
   "No_channels_yet": "Vous ne faites partie d’aucun canal pour le moment.",
   "No_direct_messages_yet": "Vous n'avez pris part à aucune discussion pour le moment.",
@@ -1029,6 +1032,7 @@
   "optional": "facultatif",
   "or": "ou",
   "Order": "Ordre",
+  "Or_talk_as_anonymous": "Ou discutez de manière anonyme",
   "OS_Arch": "Architecture",
   "OS_Cpus": "Nombre de CPU",
   "OS_Freemem": "Mémoire disponible",
@@ -1216,7 +1220,7 @@
   "seconds": "secondes",
   "Secret_token": "Jeton secret",
   "Security": "Sécurité",
-  "Select_a_department": "Sélectionner un département",
+  "Select_a_department": "Sélectionner un service",
   "Select_a_user": "Sélectionner un utilisateur",
   "Select_an_avatar": "Choisissez un avatar",
   "Select_file": "Sélectionnez le fichier",
@@ -1261,6 +1265,7 @@
   "Showing_archived_results": "<p>Affichage de <b>%s</b> résultats archivés</p>",
   "Showing_online_users": "<b>__total_showing__</b> utilisateur(s) affichés sur un total de __total__",
   "Showing_results": "<p><b>%s</b> résultat(s)</p>",
+  "Sign_in_to_start_talking": "Connectez vous pour commencer à discuter",
   "since_creation": "depuis %s",
   "Site_Name": "Nom du site",
   "Site_Url": "URL du site",
@@ -1550,7 +1555,7 @@
   "Yes_leave_it": "Oui, je veux partir !",
   "Yes_mute_user": "Oui, rend muet l'utilisateur !",
   "Yes_remove_user": "Oui, éjecte l'utilisateur !",
-  "You": "Toi",
+  "You": "Vous",
   "you_are_in_preview_mode_of": "Aperçu du salon #<strong>__room_name__</strong> ",
   "You_are_logged_in_as": "Vous êtes connecté en tant que",
   "You_are_not_authorized_to_view_this_page": "Vous n'avez pas l'autorisation de voir cette page.",
diff --git a/packages/rocketchat-i18n/i18n/ko.i18n.json b/packages/rocketchat-i18n/i18n/ko.i18n.json
index 02492e8aa80cd41ca749842fdf2d6630aa591094..c02eee6cb01bfbc2afe97bc3623e8a4dce8561ad 100644
--- a/packages/rocketchat-i18n/i18n/ko.i18n.json
+++ b/packages/rocketchat-i18n/i18n/ko.i18n.json
@@ -6,17 +6,17 @@
   "403": "금지됨",
   "500": "내부 서버 오류",
   "@username": "@사용자명",
-  "@username_message": "@username <message>",
-  "__username__is_no_longer__role__defined_by__user_by_": "__ 사용자 이름 __는 __user_by__에 의해, __role__ 더 이상 없다",
-  "__username__was_set__role__by__user_by_": "__ 사용자 이름 __은 __user_by__에 의해 __role__ 설정했다",
+  "@username_message": "@사용자이름 <message>",
+  "__username__is_no_longer__role__defined_by__user_by_": "__사용자는 더이상  __가설정한  __역할이 없습니다",
+  "__username__was_set__role__by__user_by_": "__ 사용자에게 __사용자가 __역할을__ 설정하였습니다",
   "Accept": "수락",
-  "Accept_incoming_livechat_requests_even_if_there_are_no_online_agents": "온라인 상담원이없는 경우에도 들어오는 실시간 채팅 요청 수락",
-  "Access_not_authorized": "액세스 권한이 없습니다",
+  "Accept_incoming_livechat_requests_even_if_there_are_no_online_agents": "상담원이 온라인 상태가 아닌 경우에도 라이브챗을 수락합니다.",
+  "Access_not_authorized": "엑세스 권한이 없습니다",
   "Access_Token_URL": "액세스 토큰 URL",
-  "Accessing_permissions": "권한 액세스",
+  "Accessing_permissions": "접속권한",
   "Account_SID": "계정 SID",
   "Accounts": "계정",
-  "Accounts_AllowDeleteOwnAccount": "사용자가 자신의 계정을 삭제할 수 있습니다.",
+  "Accounts_AllowDeleteOwnAccount": "사용자가 자신의 계정을 삭제할 수 있습니다",
   "Accounts_AllowedDomainsList": "허용된 도메인 목록",
   "Accounts_AllowedDomainsList_Description": "허용된 도메인을 쉼표(,)로 구분하기",
   "Accounts_AllowEmailChange": "이메일 변경을 허용합니다",
@@ -29,10 +29,10 @@
   "Accounts_AvatarStorePath": "아바타 저장 경로",
   "Accounts_AvatarStoreType": "아바파 저장 타입",
   "Accounts_BlockedDomainsList": "차단된 도메인 목록",
-  "Accounts_BlockedDomainsList_Description": "차단 된 도메인의 쉼표로 구분 된 목록",
-  "Accounts_BlockedUsernameList": "차단 된 사용자 이름 목록",
-  "Accounts_BlockedUsernameList_Description": "차단 된 사용자 이름의 쉼표로 구분 된 목록 (대소 문자 구분)",
-  "Accounts_CustomFields_Description": "키는 필드 세팅의 딕셔너리(dictionary) 를 포함하는 필드 이름들이어야 합니다.\n\n예:<br/><code>{\n \"role\": {\n  \"type\": \"select\",\n  \"defaultValue\": \"student\",\n  \"options\": [\"teacher\", \"student\"],\n  \"required\": true,\n  \"modifyRecordField\": {\n   \"array\": true,\n   \"field\": \"roles\"\n  }\n },\n \"twitter\": {\n  \"type\": \"text\",\n  \"required\": true,\n  \"minLength\": 2,\n  \"maxLength\": 10\n }\n}</code> ",
+  "Accounts_BlockedDomainsList_Description": "쉼표로 구문된 차단 도메인 리스트",
+  "Accounts_BlockedUsernameList": "차단된 사용자 리스트",
+  "Accounts_BlockedUsernameList_Description": "쉼표로 구문된 차단 사용자 리스트 (대소 문자 구분)",
+  "Accounts_CustomFields_Description": "필드 설정에 포함된 필드명을 사용한 올바른 JSON 이여야 합니다.\n\n예:<br/><code>{\n \"role\": {\n  \"type\": \"select\",\n  \"defaultValue\": \"student\",\n  \"options\": [\"teacher\", \"student\"],\n  \"required\": true,\n  \"modifyRecordField\": {\n   \"array\": true,\n   \"field\": \"roles\"\n  }\n },\n \"twitter\": {\n  \"type\": \"text\",\n  \"required\": true,\n  \"minLength\": 2,\n  \"maxLength\": 10\n }\n}</code> ",
   "Accounts_denyUnverifiedEmail": "확인되지 않은 이메일 거부",
   "Accounts_EmailVerification": "이메일 확인",
   "Accounts_EmailVerification_Description": "이 기능을 사용하려면 SMTP설정이 올바르게 되어있는지 확인해주십시오.",
@@ -40,7 +40,7 @@
   "Accounts_Enrollment_Email_Default": "<h2> 에 오신 것을 환영합니다 <h1> [Site_Name] </h1></h2><p> [Site_URL]로 이동하여 오늘날 최고의 오픈 소스 채팅 솔루션을보십시오! </p>",
   "Accounts_Enrollment_Email_Description": "당신은 각각 사용자의 전체 이름, 이름 또는 성을 위해 [lname], [name], [fname]을 사용할 수 있습니다. <br /> 당신은 사용자의 이메일을 [email]을 사용할 수 있습니다.",
   "Accounts_Enrollment_Email_Subject_Default": "[Site_Name] 에 오신 것을 환영합니다 ",
-  "Accounts_ForgetUserSessionOnWindowClose": "윈도루를 닫을 때에 사용자 설정을 삭제 합니다.",
+  "Accounts_ForgetUserSessionOnWindowClose": "창을 닫을때 사용자 세션을 삭제합니다",
   "Accounts_Iframe_api_method": "API 메소드",
   "Accounts_Iframe_api_url": "API URL",
   "Accounts_iframe_enabled": "사용",
@@ -61,8 +61,11 @@
   "Accounts_OAuth_Custom_Token_Path": "Token 경로",
   "Accounts_OAuth_Custom_Token_Sent_Via": "토큰 보낸 비아",
   "Accounts_OAuth_Custom_Username_Field": "사용자 이름 필드",
-  "Accounts_OAuth_Drupal": "Drupal Login 이 활성화 됨",
-  "Accounts_OAuth_Facebook": "Facebook 로그인",
+  "Accounts_OAuth_Drupal": "듀팔 로그인 이 활성화 되었습니다.",
+  "Accounts_OAuth_Drupal_callback_url": "듀팔 oAuth2 리다이렉트 URI",
+  "Accounts_OAuth_Drupal_id": "듀팔 oAuth2 클라이언트 ID",
+  "Accounts_OAuth_Drupal_secret": "듀팔 oAuth2 클라이언트 비밀번호",
+  "Accounts_OAuth_Facebook": "페이스북 로그인",
   "Accounts_OAuth_Facebook_callback_url": "페이스 북 콜백 URL",
   "Accounts_OAuth_Facebook_id": "Facebook 앱 ID",
   "Accounts_OAuth_Facebook_secret": "Facebook 암호",
@@ -101,6 +104,7 @@
   "Accounts_PasswordReset": "암호 재설정",
   "Accounts_OAuth_Proxy_host": "프록시 서버",
   "Accounts_OAuth_Proxy_services": "프록시 서비스",
+  "Accounts_Registration_AuthenticationServices_Default_Roles": "인증서비스용 기본 역할",
   "Accounts_Registration_AuthenticationServices_Enabled": "인증 서비스에 등록",
   "Accounts_RegistrationForm": "등록 양식",
   "Accounts_RegistrationForm_Disabled": "비활성화",
@@ -111,43 +115,43 @@
   "Accounts_RegistrationForm_SecretURL_Description": "당신은 당신의 등록 URL에 추가됩니다 임의의 문자열을 제공해야합니다. 예 : https://demo.rocket.chat/register/[secret_hash]",
   "Accounts_RequireNameForSignUp": "회원 가입은 이름 필요",
   "Accounts_SetDefaultAvatar": "기본 아바타 설정",
-  "Accounts_ShowFormLogin": "보기 양식 기반 로그인",
-  "Accounts_UseDefaultBlockedDomainsList": "기본값 사용 차단 된 도메인 목록",
-  "Accounts_UseDNSDomainCheck": "DNS 도메인 확인을 사용하여",
+  "Accounts_ShowFormLogin": "폼방식 로그인 보기",
+  "Accounts_UseDefaultBlockedDomainsList": "기본 차단 도메인리스트 사용",
+  "Accounts_UseDNSDomainCheck": "DNS 도메인 확인 사용",
   "Accounts_UserAddedEmail_Default": "<h2> 에 오신 것을 환영합니다 <h1> [Site_Name] </h1></h2><p> [Site_URL]로 이동하여 오늘날 최고의 오픈 소스 채팅 솔루션을보십시오! </p><p> [email]과 비밀번호 : [password] 당신은 당신의 이메일을 사용하여 로그인 할 수 있습니다. 당신은 처음 로그인 후 변경해야 할 수 있습니다.",
   "Accounts_UserAddedEmail_Description": "다음과 같은 자리를 사용할 수 있습니다 : <br /><ul><li> 각각 사용자의 전체 이름, 이름 또는 성을위한 [name], [fname], [lname]. </li><li> 사용자의 이메일 [email]. </li><li> 사용자의 비밀번호 [password]. </li><li> [Site_Name]와 [Site_URL] 각각 응용 프로그램 이름 및 URL합니다. </li></ul>",
-  "Accounts_UserAddedEmailSubject_Default": "당신이 추가되었습니다 [Site_Name]",
+  "Accounts_UserAddedEmailSubject_Default": "당신이은 [Site_Name] 에 추가되었습니다",
   "Activate": "활성화",
   "Activity": "활동",
   "Add": "추가",
-  "Add_agent": "에이전트를 추가",
+  "Add_agent": "상담사 추가",
   "Add_custom_oauth": "사용자 정의 OAuth 추가",
   "Add_Domain": "도메인 추가",
   "Add_manager": "관리자 추가",
   "Add_user": "사용자 추가",
   "Add_User": "사용자 추가",
   "Add_users": "사용자 추가",
-  "Adding_OAuth_Services": "OAuth는 서비스 추가",
-  "Adding_permission": "권한을 추가",
-  "Adding_user": "추가 사용자",
-  "Additional_emails": "추가 E - 메일",
+  "Adding_OAuth_Services": "OAuth 서비스 추가",
+  "Adding_permission": "권한 추가",
+  "Adding_user": "사용자 추가",
+  "Additional_emails": "추가 이메일",
   "Additional_Feedback": "추가 의견",
   "Administration": "관리",
-  "After_OAuth2_authentication_users_will_be_redirected_to_this_URL": "OAuth2를 인증 한 후, 사용자는이 URL로 리디렉션됩니다",
-  "Agent": "에이전트",
-  "Agent_added": "에이전트는 추가",
-  "Agent_removed": "에이전트 제거",
+  "After_OAuth2_authentication_users_will_be_redirected_to_this_URL": "Oauth2 인증후 사용자는 이 URL로 이동됩니다",
+  "Agent": "상담사",
+  "Agent_added": "상담사가 추가되었습니다",
+  "Agent_removed": "상담사가 삭제되었습니다",
   "Alias": "별명",
-  "Alias_Format": "별칭 형식",
+  "Alias_Format": "별명 형식",
   "Alias_Set": "별칭 설정",
   "All": "모든",
   "All_channels": "모든 채널",
   "All_logs": "모든 로그",
   "All_messages": "모든 메시지",
-  "Allow_Invalid_SelfSigned_Certs": "잘못된 Self-Signed Certs 허용",
-  "Allow_Invalid_SelfSigned_Certs_Description": "링크 확인 및 미리보기 무효 및 자체 서명 된 SSL 인증서의 허용.",
-  "Allow_switching_departments": "방문자가 부서를 변경할 수 있도록 허용함",
-  "Analytics_features_enabled": "기능 활성화",
+  "Allow_Invalid_SelfSigned_Certs": "잘못된 자체서명 Certs 를 허용합니다",
+  "Allow_Invalid_SelfSigned_Certs_Description": "링크확인 과 프리뷰에 잘못된 자체서명 Certs 를 허용합니다.",
+  "Allow_switching_departments": "방문자가 부서를 변경할 수 있도록 허용합니다",
+  "Analytics_features_enabled": "기능이 활성화 되었습니다",
   "Analytics_features_messages_Description": "사용자가 메시지에 대해 수행 행동과 관련된 사용자 정의 이벤트를 추적합니다.",
   "Analytics_features_rooms_Description": "채널 또는 그룹 (삭제두고 작성)에 대한 작업에 관련된 사용자 정의 이벤트를 추적합니다.",
   "Analytics_features_users_Description": "사용자 (암호 재설정 시간, 프로필 사진 변경 등)에 관련 작업에 관련된 사용자 정의 이벤트를 추적합니다.",
diff --git a/packages/rocketchat-i18n/i18n/no.i18n.json b/packages/rocketchat-i18n/i18n/no.i18n.json
index d1f6bfb211eccddde2a17c9c3008d8043a9e8833..e71892775f9f6456fcbe31f9c0484a76d624eaa7 100644
--- a/packages/rocketchat-i18n/i18n/no.i18n.json
+++ b/packages/rocketchat-i18n/i18n/no.i18n.json
@@ -1,3 +1,68 @@
 {
-  "0_Errors_Only": "0 - Kun Feil"
+  "0_Errors_Only": "0 - Kun Feil",
+  "1_Errors_and_Information": "1 - Feil og informasjon",
+  "2_Erros_Information_and_Debug": "2 - Feil, Informasjon og Feilsøking",
+  "403": "Forbudt",
+  "500": "Intern server feil",
+  "Accept": "Aksepter",
+  "Accept_incoming_livechat_requests_even_if_there_are_no_online_agents": "Aksepter innkommende livechat selv om det ikke er noen online",
+  "Accept_with_no_online_agents": "Aksepter uten online agenter",
+  "Access_not_authorized": "Aksepter uautoriserte",
+  "Accounts_Enrollment_Email_Subject_Default": "Velkommen til [Site_Name]",
+  "Accounts_PasswordReset": "Reset passord",
+  "Accounts_RegistrationForm_Disabled": "Deaktivert",
+  "Accounts_RegistrationForm_Public": "Offentlig",
+  "Activate": "Aktiver",
+  "Activity": "Aktivitet",
+  "Add": "Legg til",
+  "Add_agent": "Legg til agent",
+  "Add_Domain": "Legg til domene",
+  "Add_manager": "Legg til leder",
+  "Add_user": "Legg til bruker",
+  "Add_User": "Legg til bruker",
+  "Add_users": "Legg til brukere",
+  "Adding_permission": "Legger til rettigheter",
+  "Adding_user": "Legger til bruker",
+  "Additional_emails": "Ekstra e-postadresser",
+  "Additional_Feedback": "Ekstra tilbakemelding",
+  "Administration": "Administrasjon",
+  "Agent": "Agent",
+  "Agent_added": "Lagt til agent",
+  "Agent_removed": "Fjernet agent",
+  "All": "Alle",
+  "All_channels": "Alle kanaler",
+  "All_logs": "Aller logger",
+  "All_messages": "Alle meldinger",
+  "Archive": "Arkiv",
+  "are_also_typing": "skriver også",
+  "are_typing": "skriver",
+  "Are_you_sure": "Er du sikker?",
+  "Author": "Forfatter",
+  "Available": "Tilgjengelig",
+  "Available_agents": "Tilgjengelige agenter",
+  "away": "borte",
+  "Away": "Borte",
+  "away_female": "borte",
+  "Away_female": "Borte",
+  "away_male": "borte",
+  "Away_male": "Borte",
+  "Back": "Tilbake",
+  "Back_to_applications": "Tilbake til programmer",
+  "Back_to_integrations": "Tilbake til integrasjoner",
+  "Back_to_login": "Tilbake til login",
+  "Back_to_permissions": "Tilbake til rettigheter",
+  "Block_User": "Blokker bruker",
+  "bold": "fet",
+  "busy": "opptatt",
+  "Busy": "Opptatt",
+  "busy_female": "opptatt",
+  "Busy_female": "Opptatt",
+  "busy_male": "opptatt",
+  "Busy_male": "Opptatt",
+  "by": "av",
+  "Content": "Innhold",
+  "Cancel": "Avbryt",
+  "Cancel_message_input": "Avbryt",
+  "channel": "kanal",
+  "Channel": "Kanal"
 }
\ No newline at end of file
diff --git a/packages/rocketchat-ldap/server/sync.js b/packages/rocketchat-ldap/server/sync.js
index df3ec4bf828bd9b084fafb3d6b1b90224370880f..06763c0a97e6526263fb7e24728b3618bb9af608 100644
--- a/packages/rocketchat-ldap/server/sync.js
+++ b/packages/rocketchat-ldap/server/sync.js
@@ -65,15 +65,15 @@ getDataToSyncUserData = function getDataToSyncUserData(ldapUser, user) {
 	if (syncUserData && syncUserDataFieldMap) {
 		const fieldMap = JSON.parse(syncUserDataFieldMap);
 		const userData = {};
-
 		const emailList = [];
 		_.map(fieldMap, function(userField, ldapField) {
-			if (!ldapUser.object.hasOwnProperty(ldapField)) {
-				return;
-			}
-
 			switch (userField) {
 				case 'email':
+					if (!ldapUser.object.hasOwnProperty(ldapField)) {
+						logger.debug(`user does not have attribute: ${ ldapField }`);
+						return;
+					}
+
 					if (_.isObject(ldapUser.object[ldapField])) {
 						_.map(ldapUser.object[ldapField], function(item) {
 							emailList.push({ address: item, verified: true });
@@ -84,8 +84,37 @@ getDataToSyncUserData = function getDataToSyncUserData(ldapUser, user) {
 					break;
 
 				case 'name':
-					if (user.name !== ldapUser.object[ldapField]) {
-						userData.name = ldapUser.object[ldapField];
+					const templateRegex = /#{(\w+)}/gi;
+					let match = templateRegex.exec(ldapField);
+					let tmpLdapField = ldapField;
+
+					if (match == null) {
+						if (!ldapUser.object.hasOwnProperty(ldapField)) {
+							logger.debug(`user does not have attribute: ${ ldapField }`);
+							return;
+						}
+						tmpLdapField = ldapUser.object[ldapField];
+					} else {
+						logger.debug('template found. replacing values');
+						while (match != null) {
+							const tmplVar = match[0];
+							const tmplAttrName = match[1];
+
+							if (!ldapUser.object.hasOwnProperty(tmplAttrName)) {
+								logger.debug(`user does not have attribute: ${ tmplAttrName }`);
+								return;
+							}
+
+							const attrVal = ldapUser.object[tmplAttrName];
+							logger.debug(`replacing template var: ${ tmplVar } with value from ldap: ${ attrVal }`);
+							tmpLdapField = tmpLdapField.replace(tmplVar, attrVal);
+							match = templateRegex.exec(ldapField);
+						}
+					}
+
+					if (user.name !== tmpLdapField) {
+						userData.name = tmpLdapField;
+						logger.debug(`user.name changed to: ${ tmpLdapField }`);
 					}
 					break;
 			}
diff --git a/packages/rocketchat-lib/client/lib/roomTypes.js b/packages/rocketchat-lib/client/lib/roomTypes.js
index 9cfc41585961c5713d53ed8b22ba0e8d99c184e9..fdc926c063da070bddc26e0ed9bd95dbcfb640b8 100644
--- a/packages/rocketchat-lib/client/lib/roomTypes.js
+++ b/packages/rocketchat-lib/client/lib/roomTypes.js
@@ -22,7 +22,7 @@ RocketChat.roomTypes = new class extends roomTypesCommon {
 		return _.map(list, (t) => t.identifier);
 	}
 	getUserStatus(roomType, roomId) {
-		this.roomTypes[roomType] && typeof this.roomTypes[roomType].getUserStatus === 'function' && this.roomTypes[roomType].getUserStatus(roomId);
+		return this.roomTypes[roomType] && typeof this.roomTypes[roomType].getUserStatus === 'function' && this.roomTypes[roomType].getUserStatus(roomId);
 	}
 	findRoom(roomType, identifier, user) {
 		return this.roomTypes[roomType] && this.roomTypes[roomType].findRoom(identifier, user);
diff --git a/packages/rocketchat-lib/lib/settings.js b/packages/rocketchat-lib/lib/settings.js
index 9c14943dac63ee4a0d30ae4b36efb2f98dd2d49d..ecdd2c87c4b3e663c5d6129c4f09352c38c2f161 100644
--- a/packages/rocketchat-lib/lib/settings.js
+++ b/packages/rocketchat-lib/lib/settings.js
@@ -63,9 +63,9 @@ RocketChat.settings = {
 		return _(actions).reduceRight(_.wrap, (err, success) => callback(err, success))();
 	},
 	load(key, value, initialLoad) {
-		['*', key].forEach(key => {
-			if (RocketChat.settings.callbacks[key]) {
-				RocketChat.settings.callbacks[key].forEach(callback => callback(key, value, initialLoad));
+		['*', key].forEach(item => {
+			if (RocketChat.settings.callbacks[item]) {
+				RocketChat.settings.callbacks[item].forEach(callback => callback(key, value, initialLoad));
 			}
 		});
 		Object.keys(RocketChat.settings.regexCallbacks).forEach(cbKey => {
diff --git a/packages/rocketchat-lib/rocketchat.info b/packages/rocketchat-lib/rocketchat.info
index ce43333a4eb3d13b5c1194a345a71480e9311583..fbad42938bafe8b016313731862d336e9f2bad8d 100644
--- a/packages/rocketchat-lib/rocketchat.info
+++ b/packages/rocketchat-lib/rocketchat.info
@@ -1,3 +1,3 @@
 {
-	"version": "0.56.0-develop"
+	"version": "0.57.0-develop"
 }
diff --git a/packages/rocketchat-lib/server/lib/PushNotification.js b/packages/rocketchat-lib/server/lib/PushNotification.js
index fd1bdb8db6047f74c112d76ed6a8da96a97eddba..fca87e6455a869ddca2569533480a4802b12dec1 100644
--- a/packages/rocketchat-lib/server/lib/PushNotification.js
+++ b/packages/rocketchat-lib/server/lib/PushNotification.js
@@ -16,7 +16,7 @@ class PushNotification {
 		return hash;
 	}
 
-	send({ roomName, roomId, username, message, usersTo, payload }) {
+	send({ roomName, roomId, username, message, usersTo, payload, badge = 1 }) {
 		let title;
 		if (roomName && roomName !== '') {
 			title = `${ roomName }`;
@@ -27,7 +27,7 @@ class PushNotification {
 		const icon = RocketChat.settings.get('Assets_favicon_192').url || RocketChat.settings.get('Assets_favicon_192').defaultUrl;
 		const config = {
 			from: 'push',
-			badge: 1,
+			badge,
 			sound: 'default',
 			title,
 			text: message,
diff --git a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js
index ea6ef470118a7f2b16568127f7e02eb2154c4700..b4802e357f85740c9ba1cef4042f87e89cd899d7 100644
--- a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js
+++ b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js
@@ -1,6 +1,14 @@
 /* globals Push */
 import moment from 'moment';
 
+function getBadgeCount(userId) {
+	const subscriptions = RocketChat.models.Subscriptions.findUnreadByUserId(userId).fetch();
+
+	return subscriptions.reduce((unread, sub) => {
+		return sub.unread + unread;
+	}, 0);
+}
+
 RocketChat.callbacks.add('afterSaveMessage', function(message, room) {
 	// skips this callback if the message was edited
 	if (message.editedAt) {
@@ -152,6 +160,7 @@ RocketChat.callbacks.add('afterSaveMessage', function(message, room) {
 					roomId: message.rid,
 					username: push_username,
 					message: push_message,
+					badge: getBadgeCount(userOfMention._id),
 					payload: {
 						host: Meteor.absoluteUrl(),
 						rid: message.rid,
@@ -299,23 +308,25 @@ RocketChat.callbacks.add('afterSaveMessage', function(message, room) {
 
 		if (userIdsToPushNotify.length > 0) {
 			if (Push.enabled === true) {
-				RocketChat.PushNotification.send({
-					roomId: message.rid,
-					roomName: push_room,
-					username: push_username,
-					message: push_message,
-					payload: {
-						host: Meteor.absoluteUrl(),
-						rid: message.rid,
-						sender: message.u,
-						type: room.t,
-						name: room.name
-					},
-					usersTo: {
-						userId: {
-							$in: userIdsToPushNotify
+				// send a push notification for each user individually (to get his/her badge count)
+				userIdsToPushNotify.forEach((userIdToNotify) => {
+					RocketChat.PushNotification.send({
+						roomId: message.rid,
+						roomName: push_room,
+						username: push_username,
+						message: push_message,
+						badge: getBadgeCount(userIdToNotify),
+						payload: {
+							host: Meteor.absoluteUrl(),
+							rid: message.rid,
+							sender: message.u,
+							type: room.t,
+							name: room.name
+						},
+						usersTo: {
+							userId: userIdToNotify
 						}
-					}
+					});
 				});
 			}
 		}
diff --git a/packages/rocketchat-lib/server/models/Subscriptions.coffee b/packages/rocketchat-lib/server/models/Subscriptions.coffee
index 95f12bff51ff2b341bc873211297db331cc5f31e..2eb4f13d484a3bf40ba3c8381b718ae7fc644ee6 100644
--- a/packages/rocketchat-lib/server/models/Subscriptions.coffee
+++ b/packages/rocketchat-lib/server/models/Subscriptions.coffee
@@ -126,6 +126,14 @@ class ModelSubscriptions extends RocketChat.models._Base
 
 		return @find query
 
+	findUnreadByUserId: (userId) ->
+		query =
+			'u._id': userId
+			unread:
+				$gt: 0
+
+		return @find query, fields: unread: 1
+
 	# UPDATE
 	archiveByRoomId: (roomId) ->
 		query =
diff --git a/packages/rocketchat-lib/server/startup/settings.js b/packages/rocketchat-lib/server/startup/settings.js
index f222176bb388af3e33fd09fd9ab7809bd5e3622a..bd77f407b7d70c0e630c151e1142befee161be11 100644
--- a/packages/rocketchat-lib/server/startup/settings.js
+++ b/packages/rocketchat-lib/server/startup/settings.js
@@ -481,6 +481,20 @@ RocketChat.settings.addGroup('Email', function() {
 		});
 	});
 	this.section('SMTP', function() {
+		this.add('SMTP_Protocol', 'smtp', {
+			type: 'select',
+			values: [
+				{
+					key: 'smtp',
+					i18nLabel: 'smtp'
+				}, {
+					key: 'smtps',
+					i18nLabel: 'smtps'
+				}
+			],
+			env: true,
+			i18nLabel: 'Protocol'
+		});
 		this.add('SMTP_Host', '', {
 			type: 'string',
 			env: true,
@@ -491,6 +505,11 @@ RocketChat.settings.addGroup('Email', function() {
 			env: true,
 			i18nLabel: 'Port'
 		});
+		this.add('SMTP_Pool', true, {
+			type: 'boolean',
+			env: true,
+			i18nLabel: 'Pool'
+		});
 		this.add('SMTP_Username', '', {
 			type: 'string',
 			env: true,
diff --git a/packages/rocketchat-lib/server/startup/settingsOnLoadSMTP.js b/packages/rocketchat-lib/server/startup/settingsOnLoadSMTP.js
index 5a2fb4d672958b0254562900b5e0e3e76ad5288d..8a3601061aa628763199130f1c8761aea2a0def4 100644
--- a/packages/rocketchat-lib/server/startup/settingsOnLoadSMTP.js
+++ b/packages/rocketchat-lib/server/startup/settingsOnLoadSMTP.js
@@ -1,14 +1,22 @@
 const buildMailURL = _.debounce(function() {
 	console.log('Updating process.env.MAIL_URL');
+
 	if (RocketChat.settings.get('SMTP_Host')) {
-		process.env.MAIL_URL = 'smtp://';
+		process.env.MAIL_URL = `${ RocketChat.settings.get('SMTP_Protocol') }://`;
+
 		if (RocketChat.settings.get('SMTP_Username') && RocketChat.settings.get('SMTP_Password')) {
 			process.env.MAIL_URL += `${ encodeURIComponent(RocketChat.settings.get('SMTP_Username')) }:${ encodeURIComponent(RocketChat.settings.get('SMTP_Password')) }@`;
 		}
+
 		process.env.MAIL_URL += encodeURIComponent(RocketChat.settings.get('SMTP_Host'));
+
 		if (RocketChat.settings.get('SMTP_Port')) {
-			return process.env.MAIL_URL += `:${ parseInt(RocketChat.settings.get('SMTP_Port')) }`;
+			process.env.MAIL_URL += `:${ parseInt(RocketChat.settings.get('SMTP_Port')) }`;
 		}
+
+		process.env.MAIL_URL += `?pool=${ RocketChat.settings.get('SMTP_Pool') }`;
+
+		return process.env.MAIL_URL;
 	}
 }, 500);
 
@@ -34,6 +42,14 @@ RocketChat.settings.onload('SMTP_Password', function(key, value) {
 	}
 });
 
+RocketChat.settings.onload('SMTP_Protocol', function() {
+	return buildMailURL();
+});
+
+RocketChat.settings.onload('SMTP_Pool', function() {
+	return buildMailURL();
+});
+
 Meteor.startup(function() {
 	return buildMailURL();
 });
diff --git a/packages/rocketchat-livechat/app/i18n/de.i18n.json b/packages/rocketchat-livechat/app/i18n/de.i18n.json
index 2474b11a94af432dd4ce804d57cccf4c25c02241..625393ec2916c035c7c79ea01c4d942700d3c4d3 100644
--- a/packages/rocketchat-livechat/app/i18n/de.i18n.json
+++ b/packages/rocketchat-livechat/app/i18n/de.i18n.json
@@ -2,9 +2,11 @@
   "Additional_Feedback": "Zusätzliches Feedback",
   "Appearance": "Erscheinungsbild",
   "Are_you_sure_do_you_want_end_this_chat": "Sind Sie sich sicher diesen Chat zu beenden?",
+  "Are_you_sure_do_you_want_end_this_chat_and_switch_department": "Sind Sie sich sicher diesen Chat zu beenden und den Bereich zu wechseln?",
   "Cancel": "Abbrechen",
   "Change": "Ändern",
   "Chat_ended": "Chat beendet!",
+  "Choose_a_new_department": "Wähle einen neuen Bereich",
   "Close_menu": "Menü schließen",
   "Conversation_finished": "Gespräch beendet",
   "End_chat": "Chat beenden",
@@ -17,6 +19,7 @@
   "No": "Nein",
   "Options": "Optionen",
   "Please_answer_survey": "Bitte nehmen Sie sich einen Moment Zeit, um kurz einige Fragen zu dem Gespräch zu beantworten.",
+  "Please_choose_a_department": "Wähle einen Bereich",
   "Please_fill_name_and_email": "Bitte geben Sie einen Namen und eine E-Mail-Adresse ein.",
   "Powered_by": "Unterstützt von",
   "Request_video_chat": "Video-Chat anfragen",
diff --git a/packages/rocketchat-livechat/app/i18n/fr.i18n.json b/packages/rocketchat-livechat/app/i18n/fr.i18n.json
index 61c3e8275f9535f5f04d4fff47883223ea071d66..1e07751cfee850f71f29c8db077827c480b1a6f9 100644
--- a/packages/rocketchat-livechat/app/i18n/fr.i18n.json
+++ b/packages/rocketchat-livechat/app/i18n/fr.i18n.json
@@ -2,30 +2,30 @@
   "Additional_Feedback": "Commentaires supplémentaires",
   "Appearance": "Apparence",
   "Are_you_sure_do_you_want_end_this_chat": "Êtes-vous sûr de vouloir mettre fin à cette conversation ?",
-  "Are_you_sure_do_you_want_end_this_chat_and_switch_department": "Etes vous sûr de vouloir terminer ce chat en direct et changer de département ?",
+  "Are_you_sure_do_you_want_end_this_chat_and_switch_department": "Etes vous sûr de vouloir terminer ce chat en direct et changer de service ?",
   "Cancel": "Annuler",
   "Change": "Changer",
   "Chat_ended": "Conversation terminée !",
-  "Choose_a_new_department": "Choisir un nouveau département",
+  "Choose_a_new_department": "Choisir un nouveau service",
   "Close_menu": "Fermer le menu",
   "Conversation_finished": "Conversation terminée",
   "End_chat": "Mettre fin à la conversation",
-  "How_friendly_was_the_chat_agent": "L'assistant du chat était-il sympathique ?",
-  "How_knowledgeable_was_the_chat_agent": "Les réponses de l'assistant du chat était-elles adaptées ?",
-  "How_responsive_was_the_chat_agent": "L'assistant du chat a-t-il répondu à vos questions ?",
+  "How_friendly_was_the_chat_agent": "Votre interlocuteur était-il sympathique ?",
+  "How_knowledgeable_was_the_chat_agent": "Les réponses de votre interlocuteur étaient-elles adaptées ?",
+  "How_responsive_was_the_chat_agent": "Votre interlocuteur a-t-il répondu à vos questions ?",
   "How_satisfied_were_you_with_this_chat": "Êtes-vous satisfait de ce chat?",
   "Installation": "Installation",
   "New_messages": "Nouveaux messages",
   "No": "Non",
   "Options": "Options",
   "Please_answer_survey": "Merci de prendre un moment pour répondre à un sondage rapide à propos de ce chat ",
-  "Please_choose_a_department": "Merci de choisir un département",
-  "Please_fill_name_and_email": "Veuillez remplir votre nom et votre adresse e-mail",
+  "Please_choose_a_department": "Merci de choisir un service",
+  "Please_fill_name_and_email": "Veuillez saisir votre nom et votre adresse e-mail",
   "Powered_by": "Propulsé par",
   "Request_video_chat": "Demander un chat vidéo",
-  "Select_a_department": "Sélectionner un département",
-  "Switch_department": "Changer de département",
-  "Department_switched": "Département changé",
+  "Select_a_department": "Sélectionner un service",
+  "Switch_department": "Changer de service",
+  "Department_switched": "Changement de service effectué",
   "Send": "Envoyer",
   "Skip": "Passer",
   "Start_Chat": "Démarrer un chat",
diff --git a/packages/rocketchat-livechat/app/i18n/ko.i18n.json b/packages/rocketchat-livechat/app/i18n/ko.i18n.json
index 569097d1f0411eafdfb62e43a354fc82712b98e8..04b448a9da550beb7da684f58e86a97f6c95f8ba 100644
--- a/packages/rocketchat-livechat/app/i18n/ko.i18n.json
+++ b/packages/rocketchat-livechat/app/i18n/ko.i18n.json
@@ -2,30 +2,30 @@
   "Additional_Feedback": "추가 의견",
   "Appearance": "모양",
   "Are_you_sure_do_you_want_end_this_chat": "이 채팅을 정말 끝내시겠습니까?",
-  "Are_you_sure_do_you_want_end_this_chat_and_switch_department": "정말 이 채팅을 종료하고 부서를 변경하겠습니까?",
+  "Are_you_sure_do_you_want_end_this_chat_and_switch_department": "현재 진행중인 채팅을 종료하고 부서를 변경하시겠습니까?",
   "Cancel": "취소",
   "Change": "변경",
-  "Chat_ended": "채팅 종료",
-  "Choose_a_new_department": "새로운 부서를 선택하세요.",
+  "Chat_ended": "채팅이 종료되었습니다. ",
+  "Choose_a_new_department": "새 부서를 선택해주세요",
   "Close_menu": "메뉴 닫기",
   "Conversation_finished": "대화 종료됨",
   "End_chat": "채팅 끝남",
-  "How_friendly_was_the_chat_agent": "채팅 담당자는 얼마나 친절했나요?",
-  "How_knowledgeable_was_the_chat_agent": "채팅 에이전트의 기반 지식이 풍부했나요?",
-  "How_responsive_was_the_chat_agent": "채팅 담당자는 얼마나 빠르게 응답했나요?",
-  "How_satisfied_were_you_with_this_chat": "채팅에 얼마나 만족했나요?",
+  "How_friendly_was_the_chat_agent": "상담사가 친절했나요?",
+  "How_knowledgeable_was_the_chat_agent": "상담사의 관련 업무 지식이 충분했나요?",
+  "How_responsive_was_the_chat_agent": "삼담사가 빠르게 응답했나요?",
+  "How_satisfied_were_you_with_this_chat": "채팅 내용에 얼마나 만족 하였나요?",
   "Installation": "설치",
   "New_messages": "새 메시지",
   "No": "아니오",
   "Options": "옵션",
   "Please_answer_survey": "이 채팅에 대한 간단한 설문 조사에 응답하기 위해 잠시 시간을내어 주시기 바랍니다",
-  "Please_choose_a_department": "부서를 선택하세요.",
+  "Please_choose_a_department": "부서를 선택해주세요",
   "Please_fill_name_and_email": "이름과 이메일을 입력하세요",
-  "Powered_by": "에 의해 구동",
+  "Powered_by": "지원을받는",
   "Request_video_chat": "비디오채팅 요청",
   "Select_a_department": "부서를 선택해주세요",
   "Switch_department": "부서 변경",
-  "Department_switched": "부서가 변경되었습니다.",
+  "Department_switched": "부서가 변경되었습니다",
   "Send": "전송",
   "Skip": "건너뛰기",
   "Start_Chat": "채팅 시작",
@@ -33,13 +33,14 @@
   "Survey_instructions": "당신의 만족도를 평가해 주세요. 매우불만은 1, 매우 만족은 5 입니다.",
   "Thank_you_for_your_feedback": "의견을 보내 주셔서 감사합니다",
   "Thanks_We_ll_get_back_to_you_soon": "감사합니다! 곧 다시 연락드리겠습니다",
-  "transcript_sent": "채팅 내용을 발송했습니다.",
-  "Type_your_email": "이메일을 입력",
-  "Type_your_message": "메시지를 입력",
-  "Type_your_name": "당신의 이름을 입력",
-  "User_joined": "사용자 가입",
-  "User_left": "사용자 왼쪽",
-  "We_are_offline_Sorry_for_the_inconvenience": "우리는 오프라인 상태입니다. 불편을 드려 죄송합니다.",
+  "transcript_sent": "채팅 내용을 발송했습니다",
+  "Type_your_email": "이메일을 입력해주세요",
+  "Type_your_message": "메시지를 입력해주세요",
+  "Type_your_name": "이름을 입력해주세요",
+  "User_joined": "사용자가 참여하였습니다",
+  "User_left": "사용자가 떠났습니다",
+  "We_are_offline_Sorry_for_the_inconvenience": "현재 오프라인 상태입니다. 불편을 드려 죄송합니다",
   "Yes": "예",
+  "You": "당신",
   "You_must_complete_all_fields": "모든 필드를 작성해야합니다"
 }
\ No newline at end of file
diff --git a/packages/rocketchat-livechat/app/i18n/no.i18n.json b/packages/rocketchat-livechat/app/i18n/no.i18n.json
index d6955adb8572b4c553abd56118203f4cf81085bd..539088fa6dc64634a059a906eb8aa1673a967c2a 100644
--- a/packages/rocketchat-livechat/app/i18n/no.i18n.json
+++ b/packages/rocketchat-livechat/app/i18n/no.i18n.json
@@ -1,4 +1,29 @@
 {
   "Additional_Feedback": "Tilleggs Tilbakemelding",
-  "No": "Nei"
+  "Appearance": "Utseende",
+  "Are_you_sure_do_you_want_end_this_chat": "Er du sikker på at du vil avslutte denne samtalen?",
+  "Are_you_sure_do_you_want_end_this_chat_and_switch_department": "Er du sikker på at du vil avslutte denne samtalen og bytte avdeling?",
+  "Cancel": "Avbryt",
+  "Change": "Endre",
+  "Chat_ended": "Samtalen er avsluttet!",
+  "Choose_a_new_department": "Velg ny avdeling",
+  "Close_menu": "Lukk meny",
+  "Conversation_finished": "Samtalen er avsluttet",
+  "End_chat": "Avslutt samtale",
+  "How_friendly_was_the_chat_agent": "Hvor vennlig var personen du pratet med?",
+  "How_responsive_was_the_chat_agent": "Hvor raskt svarte personen du pratet med?",
+  "How_satisfied_were_you_with_this_chat": "Er du fornøyd med samtalen?",
+  "Installation": "Installasjon",
+  "New_messages": "Ny melding",
+  "No": "Nei",
+  "Options": "Egenskaper",
+  "Send": "Send",
+  "Skip": "Hopp over",
+  "Start_Chat": "Start samtale",
+  "Survey": "Undersøkelse",
+  "Type_your_message": "Skriv inn din beskjed",
+  "Type_your_name": "Skriv inn ditt navn",
+  "Yes": "Ja",
+  "You": "Deg",
+  "You_must_complete_all_fields": "Du må fylle inn alle feltene"
 }
\ No newline at end of file
diff --git a/packages/rocketchat-slashcommands-mute/server/mute.js b/packages/rocketchat-slashcommands-mute/server/mute.js
index 38273ff18feeea43344ccd36e2e675efd4caeeef..21601cebe0807cc5ea9a2d02f4cb79240fb37f17 100644
--- a/packages/rocketchat-slashcommands-mute/server/mute.js
+++ b/packages/rocketchat-slashcommands-mute/server/mute.js
@@ -26,7 +26,7 @@ RocketChat.slashCommands.add('mute', function Mute(command, params, item) {
 		});
 		return;
 	}
-	if ((room.usernames || []).includes(username)) {
+	if ((room.usernames || []).includes(username) === false) {
 		RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', {
 			_id: Random.id(),
 			rid: item.rid,
diff --git a/packages/rocketchat-slashcommands-mute/server/unmute.js b/packages/rocketchat-slashcommands-mute/server/unmute.js
index c5a3f02cfa108f79dff266141c707cf0c961a748..0d5d6a93e12e94494702711645a7143215570c2f 100644
--- a/packages/rocketchat-slashcommands-mute/server/unmute.js
+++ b/packages/rocketchat-slashcommands-mute/server/unmute.js
@@ -26,7 +26,7 @@ RocketChat.slashCommands.add('unmute', function Unmute(command, params, item) {
 			}, user.language)
 		});
 	}
-	if ((room.usernames || []).includes(username)) {
+	if ((room.usernames || []).includes(username) === false) {
 		return RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', {
 			_id: Random.id(),
 			rid: item.rid,
diff --git a/packages/rocketchat-theme/server/server.js b/packages/rocketchat-theme/server/server.js
index 940e4a1fdddfd7f53919abb4298c0725976360ee..16b254185f6a216f9efd063cebab78132f276b46 100644
--- a/packages/rocketchat-theme/server/server.js
+++ b/packages/rocketchat-theme/server/server.js
@@ -71,17 +71,16 @@ RocketChat.theme = new class {
 		this.compileDelayed = _.debounce(Meteor.bindEnvironment(this.compile.bind(this)), 100);
 		Meteor.startup(() => {
 			RocketChat.settings.onAfterInitialLoad(() => {
-				RocketChat.settings.get('*', Meteor.bindEnvironment((key, value) => {
+				RocketChat.settings.get(/^theme-./, Meteor.bindEnvironment((key, value) => {
 					if (key === 'theme-custom-css' && value != null) {
 						this.customCSS = value;
-					} else if (/^theme-.+/.test(key) === true) {
+					} else {
 						const name = key.replace(/^theme-[a-z]+-/, '');
 						if (this.variables[name] != null) {
 							this.variables[name].value = value;
 						}
-					} else {
-						return;
 					}
+
 					this.compileDelayed();
 				}));
 			});
@@ -89,7 +88,6 @@ RocketChat.theme = new class {
 	}
 
 	compile() {
-
 		let content = [this.getVariablesAsLess()];
 
 		content.push(...this.files.map((name) => Assets.getText(name)));
diff --git a/packages/rocketchat-ui-account/client/account.coffee b/packages/rocketchat-ui-account/client/account.coffee
deleted file mode 100644
index 21a9874c2416e37cf31dd1cf312ca400eca0ee80..0000000000000000000000000000000000000000
--- a/packages/rocketchat-ui-account/client/account.coffee
+++ /dev/null
@@ -1,4 +0,0 @@
-Template.account.onRendered ->
-	Tracker.afterFlush ->
-		SideNav.setFlex "accountFlex"
-		SideNav.openFlex()
diff --git a/packages/rocketchat-ui-account/client/account.js b/packages/rocketchat-ui-account/client/account.js
new file mode 100644
index 0000000000000000000000000000000000000000..214d55abe8c614d84b784a996c3180ca5219db58
--- /dev/null
+++ b/packages/rocketchat-ui-account/client/account.js
@@ -0,0 +1,6 @@
+Template.account.onRendered(function() {
+	Tracker.afterFlush(function() {
+		SideNav.setFlex('accountFlex');
+		SideNav.openFlex();
+	});
+});
diff --git a/packages/rocketchat-ui-account/client/accountFlex.coffee b/packages/rocketchat-ui-account/client/accountFlex.coffee
deleted file mode 100644
index 1e04bc7ce919da8250f2979a7f5823abe066cf95..0000000000000000000000000000000000000000
--- a/packages/rocketchat-ui-account/client/accountFlex.coffee
+++ /dev/null
@@ -1,21 +0,0 @@
-Template.accountFlex.events
-	'mouseenter header': ->
-		SideNav.overArrow()
-
-	'mouseleave header': ->
-		SideNav.leaveArrow()
-
-	'click header': ->
-		SideNav.closeFlex()
-
-	'click .cancel-settings': ->
-		SideNav.closeFlex()
-
-	'click .account-link': ->
-		menu.close()
-
-Template.accountFlex.helpers
-	allowUserProfileChange: ->
-		return RocketChat.settings.get("Accounts_AllowUserProfileChange")
-	allowUserAvatarChange: ->
-		return RocketChat.settings.get("Accounts_AllowUserAvatarChange")
\ No newline at end of file
diff --git a/packages/rocketchat-ui-account/client/accountFlex.js b/packages/rocketchat-ui-account/client/accountFlex.js
new file mode 100644
index 0000000000000000000000000000000000000000..7d6d789084c824f1744a4c41b8c4509d732c4ef6
--- /dev/null
+++ b/packages/rocketchat-ui-account/client/accountFlex.js
@@ -0,0 +1,27 @@
+/*globals menu */
+Template.accountFlex.events({
+	'mouseenter header'() {
+		SideNav.overArrow();
+	},
+	'mouseleave header'() {
+		SideNav.leaveArrow();
+	},
+	'click header'() {
+		SideNav.closeFlex();
+	},
+	'click .cancel-settings'() {
+		SideNav.closeFlex();
+	},
+	'click .account-link'() {
+		menu.close();
+	}
+});
+
+Template.accountFlex.helpers({
+	allowUserProfileChange() {
+		return RocketChat.settings.get('Accounts_AllowUserProfileChange');
+	},
+	allowUserAvatarChange() {
+		return RocketChat.settings.get('Accounts_AllowUserAvatarChange');
+	}
+});
diff --git a/packages/rocketchat-ui-account/client/accountPreferences.coffee b/packages/rocketchat-ui-account/client/accountPreferences.coffee
deleted file mode 100644
index 5ed2003c658c3cbd1259a8361af956d6b877accc..0000000000000000000000000000000000000000
--- a/packages/rocketchat-ui-account/client/accountPreferences.coffee
+++ /dev/null
@@ -1,145 +0,0 @@
-import toastr from 'toastr'
-Template.accountPreferences.helpers
-	audioAssets: ->
-		return RocketChat.CustomSounds && RocketChat.CustomSounds.getList && RocketChat.CustomSounds.getList() || [];
-
-	newMessageNotification: ->
-		return Meteor.user()?.settings?.preferences?.newMessageNotification || 'chime'
-
-	newRoomNotification: ->
-		return Meteor.user()?.settings?.preferences?.newRoomNotification || 'door'
-
-	languages: ->
-		languages = TAPi18n.getLanguages()
-		result = []
-		for key, language of languages
-			result.push _.extend(language, { key: key })
-		return _.sortBy(result, 'key')
-
-	userLanguage: (key) ->
-		return (Meteor.user().language or defaultUserLanguage())?.split('-').shift().toLowerCase() is key
-
-	checked: (property, value, defaultValue) ->
-		if not Meteor.user()?.settings?.preferences?[property]? and defaultValue is true
-			currentValue = value
-		else if Meteor.user()?.settings?.preferences?[property]?
-			currentValue = !!Meteor.user()?.settings?.preferences?[property]
-
-		return currentValue is value
-
-	selected: (property, value, defaultValue) ->
-		if not Meteor.user()?.settings?.preferences?[property]
-			return defaultValue is true
-		else
-			return Meteor.user()?.settings?.preferences?[property] == value
-
-	highlights: ->
-		return Meteor.user()?.settings?.preferences?['highlights']?.join(', ')
-
-	desktopNotificationEnabled: ->
-		return (KonchatNotification.notificationStatus.get() is 'granted') or (window.Notification && Notification.permission is "granted")
-
-	desktopNotificationDisabled: ->
-		return (KonchatNotification.notificationStatus.get() is 'denied') or (window.Notification && Notification.permission is "denied")
-
-	desktopNotificationDuration: ->
-		return Meteor.user()?.settings?.preferences?.desktopNotificationDuration - 0
-
-	showRoles: ->
-		return RocketChat.settings.get('UI_DisplayRoles');
-
-Template.accountPreferences.onCreated ->
-	settingsTemplate = this.parentTemplate(3)
-	settingsTemplate.child ?= []
-	settingsTemplate.child.push this
-
-	@useEmojis = new ReactiveVar not Meteor.user()?.settings?.preferences?.useEmojis? or Meteor.user().settings.preferences.useEmojis
-	instance = @
-	@autorun ->
-		if instance.useEmojis.get()
-			Tracker.afterFlush ->
-				$('#convertAsciiEmoji').show()
-		else
-			Tracker.afterFlush ->
-				$('#convertAsciiEmoji').hide()
-
-	@clearForm = ->
-		@find('#language').value = localStorage.getItem('userLanguage')
-
-	@save = ->
-		instance = @
-		data = {}
-
-		reload = false
-		selectedLanguage = $('#language').val()
-
-		if localStorage.getItem('userLanguage') isnt selectedLanguage
-			localStorage.setItem 'userLanguage', selectedLanguage
-			data.language = selectedLanguage
-			reload = true
-
-		data.newRoomNotification = $('select[name=newRoomNotification]').val()
-		data.newMessageNotification = $('select[name=newMessageNotification]').val()
-		data.useEmojis = $('input[name=useEmojis]:checked').val()
-		data.convertAsciiEmoji = $('input[name=convertAsciiEmoji]:checked').val()
-		data.saveMobileBandwidth = $('input[name=saveMobileBandwidth]:checked').val()
-		data.collapseMediaByDefault = $('input[name=collapseMediaByDefault]:checked').val()
-		data.viewMode = parseInt($('#viewMode').find('select').val())
-		data.hideUsernames = $('#hideUsernames').find('input:checked').val()
-		data.hideRoles = $('#hideRoles').find('input:checked').val()
-		data.hideFlexTab = $('#hideFlexTab').find('input:checked').val()
-		data.hideAvatars = $('#hideAvatars').find('input:checked').val()
-		data.mergeChannels = $('#mergeChannels').find('input:checked').val()
-		data.sendOnEnter = $('#sendOnEnter').find('select').val()
-		data.unreadRoomsMode = $('input[name=unreadRoomsMode]:checked').val()
-		data.autoImageLoad = $('input[name=autoImageLoad]:checked').val()
-		data.emailNotificationMode = $('select[name=emailNotificationMode]').val()
-		data.highlights = _.compact(_.map($('[name=highlights]').val().split(','), (e) -> return _.trim(e)))
-		data.desktopNotificationDuration = $('input[name=desktopNotificationDuration]').val()
-		data.unreadAlert = $('#unreadAlert').find('input:checked').val()
-
-		Meteor.call 'saveUserPreferences', data, (error, results) ->
-			if results
-				toastr.success t('Preferences_saved')
-				instance.clearForm()
-				if reload
-					setTimeout ->
-						Meteor._reload.reload()
-					, 1000
-
-			if error
-				handleError(error)
-
-Template.accountPreferences.onRendered ->
-	Tracker.afterFlush ->
-		SideNav.setFlex "accountFlex"
-		SideNav.openFlex()
-
-Template.accountPreferences.events
-	'click .submit button': (e, t) ->
-		t.save()
-
-	'change input[name=useEmojis]': (e, t) ->
-		t.useEmojis.set $(e.currentTarget).val() is '1'
-
-	'click .enable-notifications': ->
-		KonchatNotification.getDesktopPermission()
-
-	'click .test-notifications': ->
-		KonchatNotification.notify
-			duration: $('input[name=desktopNotificationDuration]').val()
-			payload:
-				sender:
-					username: 'rocket.cat'
-			title: TAPi18n.__('Desktop_Notification_Test')
-			text: TAPi18n.__('This_is_a_desktop_notification')
-
-	'change .audio': (e) ->
-		e.preventDefault()
-		audio = $(e.currentTarget).val()
-		if audio is 'none'
-			return
-
-		if audio
-			$audio = $('audio#' + audio)
-			$audio?[0]?.play()
diff --git a/packages/rocketchat-ui-account/client/accountPreferences.js b/packages/rocketchat-ui-account/client/accountPreferences.js
new file mode 100644
index 0000000000000000000000000000000000000000..bfd096e5f0dfd909e26e1914d31786cc81a285b0
--- /dev/null
+++ b/packages/rocketchat-ui-account/client/accountPreferences.js
@@ -0,0 +1,180 @@
+/*globals defaultUserLanguage, KonchatNotification */
+import toastr from 'toastr';
+Template.accountPreferences.helpers({
+	audioAssets() {
+		return (RocketChat.CustomSounds && RocketChat.CustomSounds.getList && RocketChat.CustomSounds.getList()) || [];
+	},
+	newMessageNotification() {
+		const user = Meteor.user();
+		return (user && user.settings && user.settings.preferences && user.settings.preferences.newMessageNotification) || 'chime';
+	},
+	newRoomNotification() {
+		const user = Meteor.user();
+		return (user && user.settings && user.settings.preferences && user.settings.preferences.newRoomNotification) || 'door';
+	},
+	languages() {
+		const languages = TAPi18n.getLanguages();
+
+		const result = Object.keys(languages).map((key) => {
+			const language = languages[key];
+			return _.extend(language, { key });
+		});
+
+		return _.sortBy(result, 'key');
+	},
+	userLanguage(key) {
+		const user = Meteor.user();
+		let result = undefined;
+		if (user.language) {
+			result = user.language.split('-').shift().toLowerCase() === key;
+		} else if (defaultUserLanguage()) {
+			result = defaultUserLanguage().split('-').shift().toLowerCase() === key;
+		}
+		return result;
+	},
+	checked(property, value, defaultValue) {
+		const user = Meteor.user();
+		const propertyeExists = !!(user && user.settings && user.settings.preferences && user.settings.preferences[property]);
+		let currentValue;
+		if (propertyeExists) {
+			currentValue = !!user.settings.preferences[property];
+		} else if (!propertyeExists && defaultValue === true) {
+			currentValue = value;
+		}
+		return currentValue === value;
+	},
+	selected(property, value, defaultValue) {
+		const user = Meteor.user();
+		const propertyeExists = !!(user && user.settings && user.settings.preferences && user.settings.preferences[property]);
+		if (propertyeExists) {
+			return user.settings.preferences[property] === value;
+		} else {
+			return defaultValue === true;
+		}
+	},
+	highlights() {
+		const user = Meteor.user();
+		return user && user.settings && user.settings.preferences && user.settings.preferences['highlights'] && user.settings.preferences['highlights'].join(', ');
+	},
+	desktopNotificationEnabled() {
+		return KonchatNotification.notificationStatus.get() === 'granted' || (window.Notification && Notification.permission === 'granted');
+	},
+	desktopNotificationDisabled() {
+		return KonchatNotification.notificationStatus.get() === 'denied' || (window.Notification && Notification.permission === 'denied');
+	},
+	desktopNotificationDuration() {
+		const user = Meteor.user();
+		return user && user.settings && user.settings.preferences && user.settings.preferences.desktopNotificationDuration;
+	},
+	showRoles() {
+		return RocketChat.settings.get('UI_DisplayRoles');
+	}
+});
+
+Template.accountPreferences.onCreated(function() {
+	const settingsTemplate = this.parentTemplate(3);
+	if (settingsTemplate.child == null) {
+		settingsTemplate.child = [];
+	}
+	settingsTemplate.child.push(this);
+	const user = Meteor.user();
+	if (user && user.settings && user.settings.preferences) {
+		this.useEmojis = new ReactiveVar(user.settings.preferences.desktopNotificationDuration == null || user.settings.preferences.useEmojis);
+	}
+	let instance = this;
+	this.autorun(() => {
+		if (instance.useEmojis && instance.useEmojis.get()) {
+			Tracker.afterFlush(() => $('#convertAsciiEmoji').show());
+		} else {
+			Tracker.afterFlush(() => $('#convertAsciiEmoji').hide());
+		}
+	});
+	this.clearForm = function() {
+		this.find('#language').value = localStorage.getItem('userLanguage');
+	};
+	this.save = function() {
+		instance = this;
+		const data = {};
+		let reload = false;
+		const selectedLanguage = $('#language').val();
+		if (localStorage.getItem('userLanguage') !== selectedLanguage) {
+			localStorage.setItem('userLanguage', selectedLanguage);
+			data.language = selectedLanguage;
+			reload = true;
+		}
+		data.newRoomNotification = $('select[name=newRoomNotification]').val();
+		data.newMessageNotification = $('select[name=newMessageNotification]').val();
+		data.useEmojis = $('input[name=useEmojis]:checked').val();
+		data.convertAsciiEmoji = $('input[name=convertAsciiEmoji]:checked').val();
+		data.saveMobileBandwidth = $('input[name=saveMobileBandwidth]:checked').val();
+		data.collapseMediaByDefault = $('input[name=collapseMediaByDefault]:checked').val();
+		data.viewMode = parseInt($('#viewMode').find('select').val());
+		data.hideUsernames = $('#hideUsernames').find('input:checked').val();
+		data.hideRoles = $('#hideRoles').find('input:checked').val();
+		data.hideFlexTab = $('#hideFlexTab').find('input:checked').val();
+		data.hideAvatars = $('#hideAvatars').find('input:checked').val();
+		data.mergeChannels = $('#mergeChannels').find('input:checked').val();
+		data.sendOnEnter = $('#sendOnEnter').find('select').val();
+		data.unreadRoomsMode = $('input[name=unreadRoomsMode]:checked').val();
+		data.autoImageLoad = $('input[name=autoImageLoad]:checked').val();
+		data.emailNotificationMode = $('select[name=emailNotificationMode]').val();
+		data.highlights = _.compact(_.map($('[name=highlights]').val().split(','), function(e) {
+			return _.trim(e);
+		}));
+		data.desktopNotificationDuration = $('input[name=desktopNotificationDuration]').val();
+		data.unreadAlert = $('#unreadAlert').find('input:checked').val();
+		Meteor.call('saveUserPreferences', data, function(error, results) {
+			if (results) {
+				toastr.success(t('Preferences_saved'));
+				instance.clearForm();
+				if (reload) {
+					setTimeout(function() {
+						Meteor._reload.reload();
+					}, 1000);
+				}
+			}
+			if (error) {
+				return handleError(error);
+			}
+		});
+	};
+});
+
+Template.accountPreferences.onRendered(function() {
+	Tracker.afterFlush(function() {
+		SideNav.setFlex('accountFlex');
+		SideNav.openFlex();
+	});
+});
+
+Template.accountPreferences.events({
+	'click .submit button'(e, t) {
+		t.save();
+	},
+	'change input[name=useEmojis]'(e, t) {
+		t.useEmojis.set($(e.currentTarget).val() === '1');
+	},
+	'click .enable-notifications'() {
+		KonchatNotification.getDesktopPermission();
+	},
+	'click .test-notifications'() {
+		KonchatNotification.notify({
+			duration: $('input[name=desktopNotificationDuration]').val(),
+			payload: { sender: { username: 'rocket.cat' }
+			},
+			title: TAPi18n.__('Desktop_Notification_Test'),
+			text: TAPi18n.__('This_is_a_desktop_notification')
+		});
+	},
+	'change .audio'(e) {
+		e.preventDefault();
+		const audio = $(e.currentTarget).val();
+		if (audio === 'none') {
+			return;
+		}
+		if (audio) {
+			const $audio = $(`audio#${ audio }`);
+			return $audio && $audio[0] && $audio.play();
+		}
+	}
+});
diff --git a/packages/rocketchat-ui-account/client/accountProfile.coffee b/packages/rocketchat-ui-account/client/accountProfile.coffee
deleted file mode 100644
index d1e0979cb50046048f215eed555d97fe814bc0e8..0000000000000000000000000000000000000000
--- a/packages/rocketchat-ui-account/client/accountProfile.coffee
+++ /dev/null
@@ -1,203 +0,0 @@
-import toastr from 'toastr'
-Template.accountProfile.helpers
-	allowDeleteOwnAccount: ->
-		return RocketChat.settings.get('Accounts_AllowDeleteOwnAccount')
-
-	realname: ->
-		return Meteor.user().name
-
-	username: ->
-		return Meteor.user().username
-
-	email: ->
-		return Meteor.user().emails?[0]?.address
-
-	emailVerified: ->
-		return  Meteor.user().emails?[0]?.verified
-
-	allowUsernameChange: ->
-		return RocketChat.settings.get("Accounts_AllowUsernameChange") and RocketChat.settings.get("LDAP_Enable") isnt true
-
-	allowEmailChange: ->
-		return RocketChat.settings.get("Accounts_AllowEmailChange")
-
-	usernameChangeDisabled: ->
-		return t('Username_Change_Disabled')
-
-	allowPasswordChange: ->
-		return RocketChat.settings.get("Accounts_AllowPasswordChange")
-
-	passwordChangeDisabled: ->
-		return t('Password_Change_Disabled')
-
-	customFields: ->
-		return Meteor.user().customFields
-
-Template.accountProfile.onCreated ->
-	settingsTemplate = this.parentTemplate(3)
-	settingsTemplate.child ?= []
-	settingsTemplate.child.push this
-
-	@clearForm = ->
-		@find('#password').value = ''
-
-	@changePassword = (newPassword, callback) ->
-		instance = @
-		if not newPassword
-			return callback()
-
-		else
-			if !RocketChat.settings.get("Accounts_AllowPasswordChange")
-				toastr.remove();
-				toastr.error t('Password_Change_Disabled')
-				instance.clearForm()
-				return
-
-	@save = (typedPassword) ->
-		instance = @
-
-		data = { typedPassword: typedPassword }
-
-		if _.trim($('#password').val()) and RocketChat.settings.get("Accounts_AllowPasswordChange")
-			data.newPassword = $('#password').val()
-
-		if _.trim $('#realname').val()
-			data.realname = _.trim $('#realname').val()
-
-		if _.trim($('#username').val()) isnt Meteor.user().username
-			if !RocketChat.settings.get("Accounts_AllowUsernameChange")
-				toastr.remove();
-				toastr.error t('Username_Change_Disabled')
-				instance.clearForm()
-				return
-			else
-				data.username = _.trim $('#username').val()
-
-		if _.trim($('#email').val()) isnt Meteor.user().emails?[0]?.address
-			if !RocketChat.settings.get("Accounts_AllowEmailChange")
-				toastr.remove();
-				toastr.error t('Email_Change_Disabled')
-				instance.clearForm()
-				return
-			else
-				data.email = _.trim $('#email').val()
-
-		customFields = {}
-		$('[data-customfield=true]').each () ->
-			customFields[this.name] = $(this).val() or ''
-
-		Meteor.call 'saveUserProfile', data, customFields, (error, results) ->
-			if results
-				toastr.remove();
-				toastr.success t('Profile_saved_successfully')
-				swal.close()
-				instance.clearForm()
-
-			if error
-				toastr.remove();
-				handleError(error)
-
-Template.accountProfile.onRendered ->
-	Tracker.afterFlush ->
-		# this should throw an error-template
-		FlowRouter.go("home") if !RocketChat.settings.get("Accounts_AllowUserProfileChange")
-		SideNav.setFlex "accountFlex"
-		SideNav.openFlex()
-
-Template.accountProfile.events
-	'click .submit button': (e, instance) ->
-		user = Meteor.user()
-		reqPass = ((_.trim($('#email').val()) isnt user?.emails?[0]?.address) or _.trim($('#password').val())) and s.trim(user?.services?.password?.bcrypt)
-		unless reqPass
-			return instance.save()
-
-		swal
-			title: t("Please_enter_your_password"),
-			text: t("For_your_security_you_must_enter_your_current_password_to_continue"),
-			type: "input",
-			inputType: "password",
-			showCancelButton: true,
-			closeOnConfirm: false,
-			confirmButtonText: t('Save'),
-			cancelButtonText: t('Cancel')
-
-		, (typedPassword) =>
-			if typedPassword
-				toastr.remove();
-				toastr.warning(t("Please_wait_while_your_profile_is_being_saved"));
-				instance.save(SHA256(typedPassword))
-			else
-				swal.showInputError(t("You_need_to_type_in_your_password_in_order_to_do_this"));
-				return false;
-	'click .logoutOthers button': (event, templateInstance) ->
-		Meteor.logoutOtherClients (error) ->
-			if error
-				toastr.remove();
-				handleError(error)
-			else
-				toastr.remove();
-				toastr.success t('Logged_out_of_other_clients_successfully')
-	'click .delete-account button': (e) ->
-		e.preventDefault();
-		if s.trim Meteor.user()?.services?.password?.bcrypt
-			swal
-				title: t("Are_you_sure_you_want_to_delete_your_account"),
-				text: t("If_you_are_sure_type_in_your_password"),
-				type: "input",
-				inputType: "password",
-				showCancelButton: true,
-				closeOnConfirm: false,
-				confirmButtonText: t('Delete')
-				cancelButtonText: t('Cancel')
-
-			, (typedPassword) =>
-				if typedPassword
-					toastr.remove();
-					toastr.warning(t("Please_wait_while_your_account_is_being_deleted"));
-					Meteor.call 'deleteUserOwnAccount', SHA256(typedPassword), (error, results) ->
-						if error
-							toastr.remove();
-							swal.showInputError(t("Your_password_is_wrong"));
-						else
-							swal.close();
-				else
-					swal.showInputError(t("You_need_to_type_in_your_password_in_order_to_do_this"));
-					return false;
-		else
-			swal
-				title: t("Are_you_sure_you_want_to_delete_your_account"),
-				text: t("If_you_are_sure_type_in_your_username"),
-				type: "input",
-				showCancelButton: true,
-				closeOnConfirm: false,
-				confirmButtonText: t('Delete')
-				cancelButtonText: t('Cancel')
-
-			, (deleteConfirmation) =>
-				if deleteConfirmation is Meteor.user()?.username
-					toastr.remove();
-					toastr.warning(t("Please_wait_while_your_account_is_being_deleted"));
-					Meteor.call 'deleteUserOwnAccount', deleteConfirmation, (error, results) ->
-						if error
-							toastr.remove();
-							swal.showInputError(t("Your_password_is_wrong"));
-						else
-							swal.close();
-				else
-					swal.showInputError(t("You_need_to_type_in_your_username_in_order_to_do_this"));
-					return false;
-
-	'click #resend-verification-email': (e) ->
-		e.preventDefault()
-
-		e.currentTarget.innerHTML = e.currentTarget.innerHTML + ' ...'
-		e.currentTarget.disabled = true
-
-		Meteor.call 'sendConfirmationEmail', Meteor.user().emails?[0]?.address, (error, results) =>
-			if results
-				toastr.success t('Verification_email_sent')
-			else if error
-				handleError(error)
-
-			e.currentTarget.innerHTML = e.currentTarget.innerHTML.replace(' ...', '')
-			e.currentTarget.disabled = false
diff --git a/packages/rocketchat-ui-account/client/accountProfile.js b/packages/rocketchat-ui-account/client/accountProfile.js
new file mode 100644
index 0000000000000000000000000000000000000000..2038b340d2de071b9dc9ae128ae566f073dc39f8
--- /dev/null
+++ b/packages/rocketchat-ui-account/client/accountProfile.js
@@ -0,0 +1,233 @@
+import toastr from 'toastr';
+Template.accountProfile.helpers({
+	allowDeleteOwnAccount() {
+		return RocketChat.settings.get('Accounts_AllowDeleteOwnAccount');
+	},
+	realname() {
+		return Meteor.user().name;
+	},
+	username() {
+		return Meteor.user().username;
+	},
+	email() {
+		const user = Meteor.user();
+		return user.emails && user.emails[0] && user.emails[0].address;
+	},
+	emailVerified() {
+		const user = Meteor.user();
+		return user.emails && user.emails[0] && user.emails[0].verified;
+	},
+	allowUsernameChange() {
+		return RocketChat.settings.get('Accounts_AllowUsernameChange') && RocketChat.settings.get('LDAP_Enable') !== true;
+	},
+	allowEmailChange() {
+		return RocketChat.settings.get('Accounts_AllowEmailChange');
+	},
+	usernameChangeDisabled() {
+		return t('Username_Change_Disabled');
+	},
+	allowPasswordChange() {
+		return RocketChat.settings.get('Accounts_AllowPasswordChange');
+	},
+	passwordChangeDisabled() {
+		return t('Password_Change_Disabled');
+	},
+	customFields() {
+		return Meteor.user().customFields;
+	}
+});
+
+Template.accountProfile.onCreated(function() {
+	const settingsTemplate = this.parentTemplate(3);
+	if (settingsTemplate.child == null) {
+		settingsTemplate.child = [];
+	}
+	settingsTemplate.child.push(this);
+	this.clearForm = function() {
+		this.find('#password').value = '';
+	};
+	this.changePassword = function(newPassword, callback) {
+		const instance = this;
+		if (!newPassword) {
+			return callback();
+		} else if (!RocketChat.settings.get('Accounts_AllowPasswordChange')) {
+			toastr.remove();
+			toastr.error(t('Password_Change_Disabled'));
+			instance.clearForm();
+			return;
+		}
+	};
+	this.save = function(typedPassword) {
+		const instance = this;
+		const data = {
+			typedPassword
+		};
+		if (_.trim($('#password').val()) && RocketChat.settings.get('Accounts_AllowPasswordChange')) {
+			data.newPassword = $('#password').val();
+		}
+		if (_.trim($('#realname').val())) {
+			data.realname = _.trim($('#realname').val());
+		}
+		if (_.trim($('#username').val()) !== Meteor.user().username) {
+			if (!RocketChat.settings.get('Accounts_AllowUsernameChange')) {
+				toastr.remove();
+				toastr.error(t('Username_Change_Disabled'));
+				instance.clearForm();
+				return;
+			} else {
+				data.username = _.trim($('#username').val());
+			}
+		}
+		const user = Meteor.user();
+		if (_.trim($('#email').val()) !== (user.emails && user.emails[0] && user.emails[0].address)) {
+			if (!RocketChat.settings.get('Accounts_AllowEmailChange')) {
+				toastr.remove();
+				toastr.error(t('Email_Change_Disabled'));
+				instance.clearForm();
+				return;
+			} else {
+				data.email = _.trim($('#email').val());
+			}
+		}
+		const customFields = {};
+		$('[data-customfield=true]').each(function() {
+			customFields[this.name] = $(this).val() || '';
+		});
+		Meteor.call('saveUserProfile', data, customFields, function(error, results) {
+			if (results) {
+				toastr.remove();
+				toastr.success(t('Profile_saved_successfully'));
+				swal.close();
+				instance.clearForm();
+			}
+			if (error) {
+				toastr.remove();
+				return handleError(error);
+			}
+		});
+	};
+});
+
+Template.accountProfile.onRendered(function() {
+	Tracker.afterFlush(function() {
+		if (!RocketChat.settings.get('Accounts_AllowUserProfileChange')) {
+			FlowRouter.go('home');
+		}
+		SideNav.setFlex('accountFlex');
+		SideNav.openFlex();
+	});
+});
+
+Template.accountProfile.events({
+	'click .submit button'(e, instance) {
+		const user = Meteor.user();
+		const reqPass = ((_.trim($('#email').val()) !== (user && user.emails && user.emails[0] && user.emails[0].address)) || _.trim($('#password').val())) && (user && user.services && user.services.password && s.trim(user.services.password.bcrypt));
+		if (!reqPass) {
+			return instance.save();
+		}
+		swal({
+			title: t('Please_enter_your_password'),
+			text: t('For_your_security_you_must_enter_your_current_password_to_continue'),
+			type: 'input',
+			inputType: 'password',
+			showCancelButton: true,
+			closeOnConfirm: false,
+			confirmButtonText: t('Save'),
+			cancelButtonText: t('Cancel')
+		}, (typedPassword) => {
+			if (typedPassword) {
+				toastr.remove();
+				toastr.warning(t('Please_wait_while_your_profile_is_being_saved'));
+				instance.save(SHA256(typedPassword));
+			} else {
+				swal.showInputError(t('You_need_to_type_in_your_password_in_order_to_do_this'));
+				return false;
+			}
+		});
+	},
+	'click .logoutOthers button'() {
+		Meteor.logoutOtherClients(function(error) {
+			if (error) {
+				toastr.remove();
+				handleError(error);
+			} else {
+				toastr.remove();
+				toastr.success(t('Logged_out_of_other_clients_successfully'));
+			}
+		});
+	},
+	'click .delete-account button'(e) {
+		e.preventDefault();
+		const user = Meteor.user();
+		if (s.trim(user && user.services && user.services.password && user.services.password.bcrypt)) {
+			swal({
+				title: t('Are_you_sure_you_want_to_delete_your_account'),
+				text: t('If_you_are_sure_type_in_your_password'),
+				type: 'input',
+				inputType: 'password',
+				showCancelButton: true,
+				closeOnConfirm: false,
+				confirmButtonText: t('Delete'),
+				cancelButtonText: t('Cancel')
+			}, (typedPassword) => {
+				if (typedPassword) {
+					toastr.remove();
+					toastr.warning(t('Please_wait_while_your_account_is_being_deleted'));
+					Meteor.call('deleteUserOwnAccount', SHA256(typedPassword), function(error) {
+						if (error) {
+							toastr.remove();
+							swal.showInputError(t('Your_password_is_wrong'));
+						} else {
+							swal.close();
+						}
+					});
+				} else {
+					swal.showInputError(t('You_need_to_type_in_your_password_in_order_to_do_this'));
+					return false;
+				}
+			});
+		} else {
+			swal({
+				title: t('Are_you_sure_you_want_to_delete_your_account'),
+				text: t('If_you_are_sure_type_in_your_username'),
+				type: 'input',
+				showCancelButton: true,
+				closeOnConfirm: false,
+				confirmButtonText: t('Delete'),
+				cancelButtonText: t('Cancel')
+			}, (deleteConfirmation) => {
+				const user = Meteor.user();
+				if (deleteConfirmation === (user && user.username)) {
+					toastr.remove();
+					toastr.warning(t('Please_wait_while_your_account_is_being_deleted'));
+					Meteor.call('deleteUserOwnAccount', deleteConfirmation, function(error) {
+						if (error) {
+							toastr.remove();
+							swal.showInputError(t('Your_password_is_wrong'));
+						} else {
+							swal.close();
+						}
+					});
+				} else {
+					swal.showInputError(t('You_need_to_type_in_your_username_in_order_to_do_this'));
+					return false;
+				}
+			});
+		}
+	},
+	'click #resend-verification-email'(e) {
+		const user = Meteor.user();
+		e.preventDefault();
+		e.currentTarget.innerHTML = `${ e.currentTarget.innerHTML } ...`;
+		e.currentTarget.disabled = true;
+		Meteor.call('sendConfirmationEmail', user.emails && user.emails[0] && user.emails[0].address((error, results) => {
+			if (results) {
+				toastr.success(t('Verification_email_sent'));
+			} else if (error) {
+				handleError(error);
+			}
+			e.currentTarget.innerHTML = e.currentTarget.innerHTML.replace(' ...', '');
+			return e.currentTarget.disabled = false;
+		}));
+	}
+});
diff --git a/packages/rocketchat-ui-account/client/avatar/avatar.coffee b/packages/rocketchat-ui-account/client/avatar/avatar.coffee
deleted file mode 100644
index b0abe8754a17e541350f55cda4e2643c23d25398..0000000000000000000000000000000000000000
--- a/packages/rocketchat-ui-account/client/avatar/avatar.coffee
+++ /dev/null
@@ -1,14 +0,0 @@
-Template.avatar.helpers
-	imageUrl: ->
-		username = this.username
-		if not username? and this.userId?
-			username = Meteor.users.findOne(this.userId)?.username
-
-		if not username?
-			return
-
-		Session.get "avatar_random_#{username}"
-
-		url = getAvatarUrlFromUsername(username)
-
-		return "background-image:url(#{url});"
diff --git a/packages/rocketchat-ui-account/client/avatar/avatar.js b/packages/rocketchat-ui-account/client/avatar/avatar.js
new file mode 100644
index 0000000000000000000000000000000000000000..74214467ccf50b1f8fb65e3a355fdf9bb8af81c7
--- /dev/null
+++ b/packages/rocketchat-ui-account/client/avatar/avatar.js
@@ -0,0 +1,15 @@
+Template.avatar.helpers({
+	imageUrl() {
+		let username = this.username;
+		if (username == null && this.userId != null) {
+			const user = Meteor.users.findOne(this.userId);
+			username = user && user.username;
+		}
+		if (username == null) {
+			return;
+		}
+		Session.get(`avatar_random_${ username }`);
+		const url = getAvatarUrlFromUsername(username);
+		return `background-image:url(${ url });`;
+	}
+});
diff --git a/packages/rocketchat-ui-account/client/avatar/prompt.coffee b/packages/rocketchat-ui-account/client/avatar/prompt.coffee
deleted file mode 100644
index 50f3d8838fdfe26815adf9f85e9a23b04781255f..0000000000000000000000000000000000000000
--- a/packages/rocketchat-ui-account/client/avatar/prompt.coffee
+++ /dev/null
@@ -1,105 +0,0 @@
-import toastr from 'toastr'
-Template.avatarPrompt.onCreated ->
-	self = this
-	self.suggestions = new ReactiveVar
-	self.upload = new ReactiveVar
-
-	self.getSuggestions = ->
-		self.suggestions.set undefined
-		Meteor.call 'getAvatarSuggestion', (error, avatars) ->
-			self.suggestions.set
-				ready: true
-				avatars: avatars
-
-	self.getSuggestions()
-
-Template.avatarPrompt.onRendered ->
-	Tracker.afterFlush ->
-		# this should throw an error-template
-		FlowRouter.go("home") if !RocketChat.settings.get("Accounts_AllowUserAvatarChange")
-		SideNav.setFlex "accountFlex"
-		SideNav.openFlex()
-
-Template.avatarPrompt.helpers
-	suggestions: ->
-		return Template.instance().suggestions.get()
-
-	suggestAvatar: (service) ->
-		suggestions = Template.instance().suggestions.get()
-		return RocketChat.settings.get("Accounts_OAuth_#{_.capitalize service}") and not suggestions.avatars[service]
-
-	upload: ->
-		return Template.instance().upload.get()
-
-	username: ->
-		return Meteor.user()?.username
-
-	initialsUsername: ->
-		return '@'+Meteor.user()?.username
-
-Template.avatarPrompt.events
-	'click .select-service': ->
-		if @service is 'initials'
-			Meteor.call 'resetAvatar', (err) ->
-				if err?.details?.timeToReset?
-					toastr.error t('error-too-many-requests', { seconds: parseInt(err.details.timeToReset / 1000) })
-				else
-					toastr.success t('Avatar_changed_successfully')
-					RocketChat.callbacks.run('userAvatarSet', 'initials')
-		else if @service is 'url'
-			if _.trim $('#avatarurl').val()
-				Meteor.call 'setAvatarFromService', $('#avatarurl').val(), '', @service, (err) ->
-					if err
-						if err.details?.timeToReset?
-							toastr.error t('error-too-many-requests', { seconds: parseInt(err.details.timeToReset / 1000) })
-						else
-							toastr.error t('Avatar_url_invalid_or_error')
-					else
-						toastr.success t('Avatar_changed_successfully')
-						RocketChat.callbacks.run('userAvatarSet', 'url')
-			else
-				toastr.error t('Please_enter_value_for_url')
-		else
-			tmpService = @service
-			Meteor.call 'setAvatarFromService', @blob, @contentType, @service, (err) ->
-				if err?.details?.timeToReset?
-					toastr.error t('error-too-many-requests', { seconds: parseInt(err.details.timeToReset / 1000) })
-				else
-					toastr.success t('Avatar_changed_successfully')
-					RocketChat.callbacks.run('userAvatarSet', tmpService)
-
-	'click .login-with-service': (event, template) ->
-		loginWithService = "loginWith#{_.capitalize(this)}"
-
-		serviceConfig = {}
-
-		Meteor[loginWithService] serviceConfig, (error) ->
-			if error?.error is 'github-no-public-email'
-				alert t("github_no_public_email")
-				return
-
-			console.log error
-			if error?
-				toastr.error error.message
-				return
-
-			template.getSuggestions()
-
-	'change .avatar-file-input': (event, template) ->
-		e = event.originalEvent or event
-		files = e.target.files
-		if not files or files.length is 0
-			files = e.dataTransfer?.files or []
-
-		for blob in files
-			if not /image\/.+/.test blob.type
-				return
-
-			reader = new FileReader()
-			reader.readAsDataURL(blob)
-			reader.onloadend = ->
-				template.upload.set
-					service: 'upload'
-					contentType: blob.type
-					blob: reader.result
-				RocketChat.callbacks.run('userAvatarSet', 'upload')
diff --git a/packages/rocketchat-ui-account/client/avatar/prompt.js b/packages/rocketchat-ui-account/client/avatar/prompt.js
new file mode 100644
index 0000000000000000000000000000000000000000..b2a02bb5b8854a4e60d1271d444c53bcbd99cb9c
--- /dev/null
+++ b/packages/rocketchat-ui-account/client/avatar/prompt.js
@@ -0,0 +1,129 @@
+import toastr from 'toastr';
+Template.avatarPrompt.onCreated(function() {
+	const self = this;
+	self.suggestions = new ReactiveVar;
+	self.upload = new ReactiveVar;
+	self.getSuggestions = function() {
+		self.suggestions.set(undefined);
+		Meteor.call('getAvatarSuggestion', function(error, avatars) {
+			self.suggestions.set({ ready: true, avatars });
+		});
+	};
+	self.getSuggestions();
+});
+
+Template.avatarPrompt.onRendered(function() {
+	Tracker.afterFlush(function() {
+		if (!RocketChat.settings.get('Accounts_AllowUserAvatarChange')) {
+			FlowRouter.go('home');
+		}
+		SideNav.setFlex('accountFlex');
+		SideNav.openFlex();
+	});
+});
+
+Template.avatarPrompt.helpers({
+	suggestions() {
+		return Template.instance().suggestions.get();
+	},
+	suggestAvatar(service) {
+		const suggestions = Template.instance().suggestions.get();
+		return RocketChat.settings.get(`Accounts_OAuth_${ _.capitalize(service) }`) && !suggestions.avatars[service];
+	},
+	upload() {
+		return Template.instance().upload.get();
+	},
+	username() {
+		const user = Meteor.user();
+		return user && user.username;
+	},
+	initialsUsername() {
+		const user = Meteor.user();
+		return `@${ user && user.username }`;
+	}
+});
+
+Template.avatarPrompt.events({
+	'click .select-service'() {
+		if (this.service === 'initials') {
+			Meteor.call('resetAvatar', function(err) {
+				if (err && err.details.timeToReset && err.details.timeToReset) {
+					toastr.error(t('error-too-many-requests', {
+						seconds: parseInt(err.details.timeToReset / 1000)
+					}));
+				} else {
+					toastr.success(t('Avatar_changed_successfully'));
+					RocketChat.callbacks.run('userAvatarSet', 'initials');
+				}
+			});
+		} else if (this.service === 'url') {
+			if (_.trim($('#avatarurl').val())) {
+				Meteor.call('setAvatarFromService', $('#avatarurl').val(), '', this.service, function(err) {
+					if (err) {
+						if (err.details.timeToReset && err.details.timeToReset) {
+							toastr.error(t('error-too-many-requests', {
+								seconds: parseInt(err.details.timeToReset / 1000)
+							}));
+						} else {
+							toastr.error(t('Avatar_url_invalid_or_error'));
+						}
+					} else {
+						toastr.success(t('Avatar_changed_successfully'));
+						RocketChat.callbacks.run('userAvatarSet', 'url');
+					}
+				});
+			} else {
+				toastr.error(t('Please_enter_value_for_url'));
+			}
+		} else {
+			const tmpService = this.service;
+			Meteor.call('setAvatarFromService', this.blob, this.contentType, this.service, function(err) {
+				if (err && err.details.timeToReset && err.details.timeToReset) {
+					toastr.error(t('error-too-many-requests', {
+						seconds: parseInt(err.details.timeToReset / 1000)
+					}));
+				} else {
+					toastr.success(t('Avatar_changed_successfully'));
+					RocketChat.callbacks.run('userAvatarSet', tmpService);
+				}
+			});
+		}
+	},
+	'click .login-with-service'(event, template) {
+		const loginWithService = `loginWith${ _.capitalize(this) }`;
+		const serviceConfig = {};
+		Meteor[loginWithService](serviceConfig, function(error) {
+			if (error && error.error) {
+				if (error.error === 'github-no-public-email') {
+					return alert(t('github_no_public_email'));
+				}
+				console.log(error);
+				return toastr.error(error.message);
+			}
+			template.getSuggestions();
+		});
+	},
+	'change .avatar-file-input'(event, template) {
+		const e = event.originalEvent || event;
+		let files = e.target.files;
+		if (!files || files.length === 0) {
+			files = (e.dataTransfer && e.dataTransfer.files) || [];
+		}
+		Object.keys(files).forEach(key => {
+			const blob = files[key];
+			if (!/image\/.+/.test(blob.type)) {
+				return;
+			}
+			const reader = new FileReader();
+			reader.readAsDataURL(blob);
+			reader.onloadend = function() {
+				template.upload.set({
+					service: 'upload',
+					contentType: blob.type,
+					blob: reader.result
+				});
+				RocketChat.callbacks.run('userAvatarSet', 'upload');
+			};
+		});
+	}
+});
diff --git a/packages/rocketchat-ui-account/package.js b/packages/rocketchat-ui-account/package.js
index 29966722f51b55ef5bbc9d3e255079ca26454ef4..b461ce56a88373206c334e56bd41339f48132cca 100644
--- a/packages/rocketchat-ui-account/package.js
+++ b/packages/rocketchat-ui-account/package.js
@@ -14,7 +14,6 @@ Package.onUse(function(api) {
 	api.use([
 		'ecmascript',
 		'templating',
-		'coffeescript',
 		'underscore',
 		'rocketchat:lib',
 		'sha'
@@ -27,12 +26,12 @@ Package.onUse(function(api) {
 	api.addFiles('client/avatar/avatar.html', 'client');
 	api.addFiles('client/avatar/prompt.html', 'client');
 
-	api.addFiles('client/account.coffee', 'client');
-	api.addFiles('client/accountFlex.coffee', 'client');
-	api.addFiles('client/accountPreferences.coffee', 'client');
-	api.addFiles('client/accountProfile.coffee', 'client');
-	api.addFiles('client/avatar/avatar.coffee', 'client');
-	api.addFiles('client/avatar/prompt.coffee', 'client');
+	api.addFiles('client/account.js', 'client');
+	api.addFiles('client/accountFlex.js', 'client');
+	api.addFiles('client/accountPreferences.js', 'client');
+	api.addFiles('client/accountProfile.js', 'client');
+	api.addFiles('client/avatar/avatar.js', 'client');
+	api.addFiles('client/avatar/prompt.js', 'client');
 
 	// api.addAssets('styles/side-nav.less', 'client');
 });
diff --git a/packages/rocketchat-ui-admin/client/adminInfo.coffee b/packages/rocketchat-ui-admin/client/adminInfo.coffee
index 51cea0151143e0ea030a56660898f75a0c0f4049..1bfaedf4c899ec0fda22f0cc9d51990bd139fa6f 100644
--- a/packages/rocketchat-ui-admin/client/adminInfo.coffee
+++ b/packages/rocketchat-ui-admin/client/adminInfo.coffee
@@ -5,6 +5,8 @@ Template.adminInfo.helpers
 		return Template.instance().ready.get()
 	statistics: ->
 		return Template.instance().statistics.get()
+	instances: ->
+		return Template.instance().instances.get()
 	inGB: (size) ->
 		if size > 1073741824
 			return _.numberFormat(size / 1024 / 1024 / 1024, 2) + ' GB'
@@ -52,13 +54,20 @@ Template.adminInfo.onRendered ->
 Template.adminInfo.onCreated ->
 	instance = @
 	@statistics = new ReactiveVar {}
+	@instances = new ReactiveVar []
 	@ready = new ReactiveVar false
 
 	if RocketChat.authz.hasAllPermission('view-statistics')
 		Meteor.call 'getStatistics', (error, statistics) ->
-			instance.ready.set true
 			if error
 				handleError(error)
 			else
 				instance.statistics.set statistics
 
+			Meteor.call 'instances/get', (error, instances) ->
+				instance.ready.set true
+				if error
+					handleError(error)
+				else
+					instance.instances.set instances
+
diff --git a/packages/rocketchat-ui-admin/client/adminInfo.html b/packages/rocketchat-ui-admin/client/adminInfo.html
index e3d5358d08ebf8ff3b1168349e6ea421d504f01a..c900a7c6dac682b4b03ce3be339b1d69a33a759b 100644
--- a/packages/rocketchat-ui-admin/client/adminInfo.html
+++ b/packages/rocketchat-ui-admin/client/adminInfo.html
@@ -226,6 +226,50 @@
 						</tr>
 					</table>
 
+					{{#if instances}}
+						<h3>{{_ "Broadcast_Connected_Instances"}}</h3>
+						{{#each instances}}
+							<table class="statistics-table secondary-background-color">
+								<tr class="admin-table-row">
+									<th class="content-background-color border-component-color">{{_ "Address"}}</th>
+									<td class="border-component-color">{{address}}</td>
+								</tr>
+								<tr class="admin-table-row">
+									<th class="content-background-color border-component-color">{{_ "Auth"}}</th>
+									<td class="border-component-color">{{broadcastAuth}}</td>
+								</tr>
+								<tr class="admin-table-row">
+									<th class="content-background-color border-component-color">{{_ "Current_Status"}} > {{_ "Connected"}}</th>
+									<td class="border-component-color">{{currentStatus.connected}}</td>
+								</tr>
+								<tr class="admin-table-row">
+									<th class="content-background-color border-component-color">{{_ "Current_Status"}} > {{_ "Retry_Count"}}</th>
+									<td class="border-component-color">{{currentStatus.retryCount}}</td>
+								</tr>
+								<tr class="admin-table-row">
+									<th class="content-background-color border-component-color">{{_ "Current_Status"}} > {{_ "Status"}}</th>
+									<td class="border-component-color">{{currentStatus.status}}</td>
+								</tr>
+								<tr class="admin-table-row">
+									<th class="content-background-color border-component-color">{{_ "Instance_Record"}} > {{_ "ID"}}</th>
+									<td class="border-component-color">{{instanceRecord._id}}</td>
+								</tr>
+								<tr class="admin-table-row">
+									<th class="content-background-color border-component-color">{{_ "Instance_Record"}} > {{_ "PID"}}</th>
+									<td class="border-component-color">{{instanceRecord.pid}}</td>
+								</tr>
+								<tr class="admin-table-row">
+									<th class="content-background-color border-component-color">{{_ "Instance_Record"}} > {{_ "Created_at"}}</th>
+									<td class="border-component-color">{{formatDate instanceRecord._createdAt}}</td>
+								</tr>
+								<tr class="admin-table-row">
+									<th class="content-background-color border-component-color">{{_ "Instance_Record"}} > {{_ "Updated_at"}}</th>
+									<td class="border-component-color">{{formatDate instanceRecord._updatedAt}}</td>
+								</tr>
+							</table>
+						{{/each}}
+					{{/if}}
+
 					<button type="button" class="button primary refresh">Refresh</button>
 				{{else}}
 					{{_ "Loading..."}}
diff --git a/packages/rocketchat-ui-message/client/message.coffee b/packages/rocketchat-ui-message/client/message.coffee
deleted file mode 100644
index 2684dfd9c0a3b4f60855e444d31b5198775f7672..0000000000000000000000000000000000000000
--- a/packages/rocketchat-ui-message/client/message.coffee
+++ /dev/null
@@ -1,254 +0,0 @@
-import moment from 'moment'
-
-Template.message.helpers
-	encodeURI: (text) ->
-		return encodeURI(text)
-	isBot: ->
-		return 'bot' if this.bot?
-	roleTags: ->
-		if not RocketChat.settings.get('UI_DisplayRoles') or Meteor.user()?.settings?.preferences?.hideRoles
-			return []
-		roles = _.union(UserRoles.findOne(this.u?._id)?.roles, RoomRoles.findOne({'u._id': this.u?._id, rid: this.rid })?.roles)
-		return RocketChat.models.Roles.find({ _id: { $in: roles }, description: { $exists: 1, $ne: '' } }, { fields: { description: 1 } })
-	isGroupable: ->
-		return 'false' if this.groupable is false
-	isSequential: ->
-		return 'sequential' if this.groupable isnt false
-	avatarFromUsername: ->
-		if this.avatar? and this.avatar[0] is '@'
-			return this.avatar.replace(/^@/, '')
-	getEmoji: (emoji) ->
-		return renderEmoji emoji
-	getName: ->
-		if this.alias
-			return this.alias
-		if RocketChat.settings.get('UI_Use_Real_Name') and this.u?.name
-			return this.u.name
-		return this.u?.username
-	showUsername: ->
-		return this.alias or (RocketChat.settings.get('UI_Use_Real_Name') and this.u?.name)
-	own: ->
-		return 'own' if this.u?._id is Meteor.userId()
-	timestamp: ->
-		return +this.ts
-	chatops: ->
-		return 'chatops-message' if this.u?.username is RocketChat.settings.get('Chatops_Username')
-	time: ->
-		return moment(this.ts).format(RocketChat.settings.get('Message_TimeFormat'))
-	date: ->
-		return moment(this.ts).format(RocketChat.settings.get('Message_DateFormat'))
-	isTemp: ->
-		if @temp is true
-			return 'temp'
-	body: ->
-		return Template.instance().body
-	system: (returnClass) ->
-		if RocketChat.MessageTypes.isSystemMessage(this)
-			if returnClass
-				return 'color-info-font-color'
-
-			return 'system'
-
-	showTranslated: ->
-		if RocketChat.settings.get('AutoTranslate_Enabled') and this.u?._id isnt Meteor.userId() and !RocketChat.MessageTypes.isSystemMessage(this)
-			subscription = RocketChat.models.Subscriptions.findOne({ rid: this.rid, 'u._id': Meteor.userId() }, { fields: { autoTranslate: 1, autoTranslateLanguage: 1 } });
-			language = RocketChat.AutoTranslate.getLanguage(this.rid);
-			return this.autoTranslateFetching || (subscription?.autoTranslate isnt this.autoTranslateShowInverse && this.translations && this.translations[language]) # || _.find(this.attachments, (attachment) -> attachment.translations && attachment.translations[language] && attachment.author_name isnt Meteor.user().username )
-
-	edited: ->
-		return Template.instance().wasEdited
-
-	editTime: ->
-		if Template.instance().wasEdited
-			return moment(@editedAt).format(RocketChat.settings.get('Message_DateFormat') + ' ' + RocketChat.settings.get('Message_TimeFormat'))
-	editedBy: ->
-		return "" unless Template.instance().wasEdited
-		# try to return the username of the editor,
-		# otherwise a special "?" character that will be
-		# rendered as a special avatar
-		return @editedBy?.username or "?"
-	canEdit: ->
-		hasPermission = RocketChat.authz.hasAtLeastOnePermission('edit-message', this.rid)
-		isEditAllowed = RocketChat.settings.get 'Message_AllowEditing'
-		editOwn = this.u?._id is Meteor.userId()
-
-		return unless hasPermission or (isEditAllowed and editOwn)
-
-		blockEditInMinutes = RocketChat.settings.get 'Message_AllowEditing_BlockEditInMinutes'
-		if blockEditInMinutes? and blockEditInMinutes isnt 0
-			msgTs = moment(this.ts) if this.ts?
-			currentTsDiff = moment().diff(msgTs, 'minutes') if msgTs?
-			return currentTsDiff < blockEditInMinutes
-		else
-			return true
-
-	canDelete: ->
-		hasPermission = RocketChat.authz.hasAtLeastOnePermission('delete-message', this.rid )
-		isDeleteAllowed = RocketChat.settings.get('Message_AllowDeleting')
-		deleteOwn = this.u?._id is Meteor.userId()
-
-		return unless hasPermission or (isDeleteAllowed and deleteOwn)
-
-		blockDeleteInMinutes = RocketChat.settings.get 'Message_AllowDeleting_BlockDeleteInMinutes'
-		if blockDeleteInMinutes? and blockDeleteInMinutes isnt 0
-			msgTs = moment(this.ts) if this.ts?
-			currentTsDiff = moment().diff(msgTs, 'minutes') if msgTs?
-			return currentTsDiff < blockDeleteInMinutes
-		else
-			return true
-
-	showEditedStatus: ->
-		return RocketChat.settings.get 'Message_ShowEditedStatus'
-	label: ->
-		if @i18nLabel
-			return t(@i18nLabel)
-		else if @label
-			return @label
-
-	hasOembed: ->
-		return false unless this.urls?.length > 0 and Template.oembedBaseWidget? and RocketChat.settings.get 'API_Embed'
-
-		return false unless this.u?.username not in RocketChat.settings.get('API_EmbedDisabledFor')?.split(',').map (username) -> username.trim()
-
-		return true
-
-	reactions: ->
-		msgReactions = []
-		userUsername = Meteor.user()?.username
-
-		for emoji, reaction of @reactions
-			total = reaction.usernames.length
-			usernames = '@' + reaction.usernames.slice(0, 15).join(', @')
-
-			usernames = usernames.replace('@'+userUsername, t('You').toLowerCase())
-
-			if total > 15
-				usernames = usernames + ' ' + t('And_more', { length: total - 15 }).toLowerCase()
-			else
-				usernames = usernames.replace(/,([^,]+)$/, ' '+t('and')+'$1')
-
-			if usernames[0] isnt '@'
-				usernames = usernames[0].toUpperCase() + usernames.substr(1)
-
-			msgReactions.push
-				emoji: emoji
-				count: reaction.usernames.length
-				usernames: usernames
-				reaction: ' ' + t('Reacted_with').toLowerCase() + ' ' + emoji
-				userReacted: reaction.usernames.indexOf(userUsername) > -1
-
-		return msgReactions
-
-	markUserReaction: (reaction) ->
-		if reaction.userReacted
-			return {
-				class: 'selected'
-			}
-
-	hideReactions: ->
-		return 'hidden' if _.isEmpty(@reactions)
-
-	actionLinks: ->
-		# remove 'method_id' and 'params' properties
-		return _.map(@actionLinks, (actionLink, key) -> _.extend({ id: key }, _.omit(actionLink, 'method_id', 'params')))
-
-	hideActionLinks: ->
-		return 'hidden' if _.isEmpty(@actionLinks)
-
-	injectIndex: (data, index) ->
-		data.index = index
-		return
-
-	hideCog: ->
-		subscription = RocketChat.models.Subscriptions.findOne({ rid: this.rid });
-		return 'hidden' if not subscription?
-
-	hideUsernames: ->
-		prefs = Meteor.user()?.settings?.preferences
-		return if prefs?.hideUsernames
-
-Template.message.onCreated ->
-	msg = Template.currentData()
-
-	@wasEdited = msg.editedAt? and not RocketChat.MessageTypes.isSystemMessage(msg)
-
-	@body = do ->
-		isSystemMessage = RocketChat.MessageTypes.isSystemMessage(msg)
-		messageType = RocketChat.MessageTypes.getType(msg)
-		if messageType?.render?
-			msg = messageType.render(msg)
-		else if messageType?.template?
-			# render template
-		else if messageType?.message?
-			if messageType.data?(msg)?
-				msg = TAPi18n.__(messageType.message, messageType.data(msg))
-			else
-				msg = TAPi18n.__(messageType.message)
-		else
-			if msg.u?.username is RocketChat.settings.get('Chatops_Username')
-				msg.html = msg.msg
-				msg = RocketChat.callbacks.run 'renderMentions', msg
-				# console.log JSON.stringify message
-				msg = msg.html
-			else
-				msg = renderMessageBody msg
-
-		if isSystemMessage
-			msg.html = RocketChat.Markdown.parse msg.html
-
-		return msg
-
-Template.message.onViewRendered = (context) ->
-	view = this
-	this._domrange.onAttached (domRange) ->
-		currentNode = domRange.lastNode()
-		currentDataset = currentNode.dataset
-		previousNode = currentNode.previousElementSibling
-		nextNode = currentNode.nextElementSibling
-		$currentNode = $(currentNode)
-		$nextNode = $(nextNode)
-
-		unless previousNode?
-			$currentNode.addClass('new-day').removeClass('sequential')
-
-		else if previousNode?.dataset?
-			previousDataset = previousNode.dataset
-			previousMessageDate = new Date(parseInt(previousDataset.timestamp))
-			currentMessageDate = new Date(parseInt(currentDataset.timestamp))
-
-			if previousMessageDate.toDateString() isnt currentMessageDate.toDateString()
-				$currentNode.addClass('new-day').removeClass('sequential')
-			else
-				$currentNode.removeClass('new-day')
-
-			if previousDataset.groupable is 'false' or currentDataset.groupable is 'false'
-				$currentNode.removeClass('sequential')
-			else
-				if previousDataset.username isnt currentDataset.username or parseInt(currentDataset.timestamp) - parseInt(previousDataset.timestamp) > RocketChat.settings.get('Message_GroupingPeriod') * 1000
-					$currentNode.removeClass('sequential')
-				else if not $currentNode.hasClass 'new-day'
-					$currentNode.addClass('sequential')
-
-		if nextNode?.dataset?
-			nextDataset = nextNode.dataset
-
-			if nextDataset.date isnt currentDataset.date
-				$nextNode.addClass('new-day').removeClass('sequential')
-			else
-				$nextNode.removeClass('new-day')
-
-			if nextDataset.groupable isnt 'false'
-				if nextDataset.username isnt currentDataset.username or parseInt(nextDataset.timestamp) - parseInt(currentDataset.timestamp) > RocketChat.settings.get('Message_GroupingPeriod') * 1000
-					$nextNode.removeClass('sequential')
-				else if not $nextNode.hasClass 'new-day'
-					$nextNode.addClass('sequential')
-
-		if not nextNode?
-			templateInstance = if $('#chat-window-' + context.rid)[0] then Blaze.getView($('#chat-window-' + context.rid)[0])?.templateInstance() else null
-
-			if currentNode.classList.contains('own') is true
-				templateInstance?.atBottom = true
-			else
-				if templateInstance?.firstNode && templateInstance?.atBottom is false
-					newMessage = templateInstance?.find(".new-message")
-					newMessage?.className = "new-message background-primary-action-color color-content-background-color "
diff --git a/packages/rocketchat-ui-message/client/message.js b/packages/rocketchat-ui-message/client/message.js
new file mode 100644
index 0000000000000000000000000000000000000000..ae7997d58303c72ab38cf424fc42e8f13264ef9a
--- /dev/null
+++ b/packages/rocketchat-ui-message/client/message.js
@@ -0,0 +1,358 @@
+/* globals renderEmoji renderMessageBody*/
+import moment from 'moment';
+
+Template.message.helpers({
+	encodeURI(text) {
+		return encodeURI(text);
+	},
+	isBot() {
+		if (this.bot != null) {
+			return 'bot';
+		}
+	},
+	roleTags() {
+		const user = Meteor.user();
+		// test user -> settings -> preferences -> hideRoles
+		if (!RocketChat.settings.get('UI_DisplayRoles') || ['settings', 'preferences', 'hideRoles'].reduce((obj, field) => typeof obj !== 'undefined' && obj[field], user)) {
+			return [];
+		}
+
+		if (!this.u || !this.u._id) {
+			return [];
+		}
+		/* globals UserRoles RoomRoles */
+		const userRoles = UserRoles.findOne(this.u._id);
+		const roomRoles = RoomRoles.findOne({
+			'u._id': this.u._id,
+			rid: this.rid
+		});
+		const roles = [...(userRoles && userRoles.roles) || [], ...(roomRoles && roomRoles.roles) || []];
+		return RocketChat.models.Roles.find({
+			_id: {
+				$in: roles
+			},
+			description: {
+				$exists: 1,
+				$ne: ''
+			}
+		}, {
+			fields: {
+				description: 1
+			}
+		});
+	},
+	isGroupable() {
+		if (this.groupable === false) {
+			return 'false';
+		}
+	},
+	isSequential() {
+		if (this.groupable !== false) {
+			return 'sequential';
+		}
+	},
+	avatarFromUsername() {
+		if ((this.avatar != null) && this.avatar[0] === '@') {
+			return this.avatar.replace(/^@/, '');
+		}
+	},
+	getEmoji(emoji) {
+		return renderEmoji(emoji);
+	},
+	getName() {
+		if (this.alias) {
+			return this.alias;
+		}
+		if (!this.u) {
+			return '';
+		}
+		return (RocketChat.settings.get('UI_Use_Real_Name') && this.u.name) || this.u.username;
+	},
+	showUsername() {
+		return this.alias || RocketChat.settings.get('UI_Use_Real_Name') && this.u && this.u.name;
+	},
+	own() {
+		if (this.u && this.u._id === Meteor.userId()) {
+			return 'own';
+		}
+	},
+	timestamp() {
+		return +this.ts;
+	},
+	chatops() {
+		if (this.u && this.u.username === RocketChat.settings.get('Chatops_Username')) {
+			return 'chatops-message';
+		}
+	},
+	time() {
+		return moment(this.ts).format(RocketChat.settings.get('Message_TimeFormat'));
+	},
+	date() {
+		return moment(this.ts).format(RocketChat.settings.get('Message_DateFormat'));
+	},
+	isTemp() {
+		if (this.temp === true) {
+			return 'temp';
+		}
+	},
+	body() {
+		return Template.instance().body;
+	},
+	system(returnClass) {
+		if (RocketChat.MessageTypes.isSystemMessage(this)) {
+			if (returnClass) {
+				return 'color-info-font-color';
+			}
+			return 'system';
+		}
+	},
+	showTranslated() {
+		if (RocketChat.settings.get('AutoTranslate_Enabled') && this.u && this.u._id !== Meteor.userId() && !RocketChat.MessageTypes.isSystemMessage(this)) {
+			const subscription = RocketChat.models.Subscriptions.findOne({
+				rid: this.rid,
+				'u._id': Meteor.userId()
+			}, {
+				fields: {
+					autoTranslate: 1,
+					autoTranslateLanguage: 1
+				}
+			});
+			const language = RocketChat.AutoTranslate.getLanguage(this.rid);
+			return this.autoTranslateFetching || subscription && subscription.autoTranslate !== this.autoTranslateShowInverse && this.translations && this.translations[language];
+		}
+	},
+	edited() {
+		return Template.instance().wasEdited;
+	},
+	editTime() {
+		if (Template.instance().wasEdited) {
+			return moment(this.editedAt).format(`${ RocketChat.settings.get('Message_DateFormat') } ${ RocketChat.settings.get('Message_TimeFormat') }`);
+		}
+	},
+	editedBy() {
+		if (!Template.instance().wasEdited) {
+			return '';
+		}
+		// try to return the username of the editor,
+		// otherwise a special "?" character that will be
+		// rendered as a special avatar
+		return (this.editedBy && this.editedBy.username) || '?';
+	},
+	canEdit() {
+		const hasPermission = RocketChat.authz.hasAtLeastOnePermission('edit-message', this.rid);
+		const isEditAllowed = RocketChat.settings.get('Message_AllowEditing');
+		const editOwn = this.u && this.u._id === Meteor.userId();
+		if (!(hasPermission || (isEditAllowed && editOwn))) {
+			return;
+		}
+		const blockEditInMinutes = RocketChat.settings.get('Message_AllowEditing_BlockEditInMinutes');
+		if (blockEditInMinutes) {
+			let msgTs;
+			if (this.ts != null) {
+				msgTs = moment(this.ts);
+			}
+			let currentTsDiff;
+			if (msgTs != null) {
+				currentTsDiff = moment().diff(msgTs, 'minutes');
+			}
+			return currentTsDiff < blockEditInMinutes;
+		} else {
+			return true;
+		}
+	},
+	canDelete() {
+		const hasPermission = RocketChat.authz.hasAtLeastOnePermission('delete-message', this.rid);
+		const isDeleteAllowed = RocketChat.settings.get('Message_AllowDeleting');
+		const deleteOwn = this.u && this.u._id === Meteor.userId();
+		if (!(hasPermission || (isDeleteAllowed && deleteOwn))) {
+			return;
+		}
+		const blockDeleteInMinutes = RocketChat.settings.get('Message_AllowDeleting_BlockDeleteInMinutes');
+		if (blockDeleteInMinutes) {
+			let msgTs;
+			if (this.ts != null) {
+				msgTs = moment(this.ts);
+			}
+			let currentTsDiff;
+			if (msgTs != null) {
+				currentTsDiff = moment().diff(msgTs, 'minutes');
+			}
+			return currentTsDiff < blockDeleteInMinutes;
+		} else {
+			return true;
+		}
+	},
+	showEditedStatus() {
+		return RocketChat.settings.get('Message_ShowEditedStatus');
+	},
+	label() {
+		if (this.i18nLabel) {
+			return t(this.i18nLabel);
+		} else if (this.label) {
+			return this.label;
+		}
+	},
+	hasOembed() {
+		if (!(this.urls && this.urls.length > 0 && Template.oembedBaseWidget != null && RocketChat.settings.get('API_Embed'))) {
+			return false;
+		}
+		if (!(RocketChat.settings.get('API_EmbedDisabledFor')||'').split(',').map(username => username.trim()).includes(this.u && this.u.username)) {
+			return false;
+		}
+		return true;
+	},
+	reactions() {
+		const userUsername = Meteor.user().username;
+		return Object.keys(this.reactions||{}).map(emoji => {
+			const reaction = this.reactions[emoji];
+			const total = reaction.usernames.length;
+			let usernames = `@${ reaction.usernames.slice(0, 15).join(', @') }`.replace(`@${ userUsername }`, t('You').toLowerCase());
+			if (total > 15) {
+				usernames = `${ usernames } ${ t('And_more', {
+					length: total - 15
+				}).toLowerCase() }`;
+			} else {
+				usernames = usernames.replace(/,([^,]+)$/, ` ${ t('and') }$1`);
+			}
+			if (usernames[0] !== '@') {
+				usernames = usernames[0].toUpperCase() + usernames.substr(1);
+			}
+			return {
+				emoji,
+				count: reaction.usernames.length,
+				usernames,
+				reaction: ` ${ t('Reacted_with').toLowerCase() } ${ emoji }`,
+				userReacted: reaction.usernames.indexOf(userUsername) > -1
+			};
+		});
+	},
+	markUserReaction(reaction) {
+		if (reaction.userReacted) {
+			return {
+				'class': 'selected'
+			};
+		}
+	},
+	hideReactions() {
+		if (_.isEmpty(this.reactions)) {
+			return 'hidden';
+		}
+	},
+	actionLinks() {
+		// remove 'method_id' and 'params' properties
+		return _.map(this.actionLinks, function(actionLink, key) {
+			return _.extend({
+				id: key
+			}, _.omit(actionLink, 'method_id', 'params'));
+		});
+	},
+	hideActionLinks() {
+		if (_.isEmpty(this.actionLinks)) {
+			return 'hidden';
+		}
+	},
+	injectIndex(data, index) {
+		data.index = index;
+	},
+	hideCog() {
+		const subscription = RocketChat.models.Subscriptions.findOne({
+			rid: this.rid
+		});
+		if (subscription == null) {
+			return 'hidden';
+		}
+	}
+});
+
+Template.message.onCreated(function() {
+	let msg = Template.currentData();
+
+	this.wasEdited = (msg.editedAt != null) && !RocketChat.MessageTypes.isSystemMessage(msg);
+
+	return this.body = (() => {
+		const isSystemMessage = RocketChat.MessageTypes.isSystemMessage(msg);
+		const messageType = RocketChat.MessageTypes.getType(msg)||{};
+		if (messageType.render) {
+			msg = messageType.render(msg);
+		} else if (messageType.template) {
+			// render template
+		} else if (messageType.message) {
+			if (typeof messageType.data === 'function' && messageType.data(msg)) {
+				msg = TAPi18n.__(messageType.message, messageType.data(msg));
+			} else {
+				msg = TAPi18n.__(messageType.message);
+			}
+		} else if (msg.u && msg.u.username === RocketChat.settings.get('Chatops_Username')) {
+			msg.html = msg.msg;
+			msg = RocketChat.callbacks.run('renderMentions', msg);
+				// console.log JSON.stringify message
+			msg = msg.html;
+		} else {
+			msg = renderMessageBody(msg);
+		}
+
+		if (isSystemMessage) {
+			msg.html = RocketChat.Markdown.parse(msg.html);
+		}
+		return msg;
+	})();
+});
+
+Template.message.onViewRendered = function(context) {
+	return this._domrange.onAttached(function(domRange) {
+		const currentNode = domRange.lastNode();
+		const currentDataset = currentNode.dataset;
+		const previousNode = currentNode.previousElementSibling;
+		const nextNode = currentNode.nextElementSibling;
+		const $currentNode = $(currentNode);
+		const $nextNode = $(nextNode);
+		if (previousNode == null) {
+			$currentNode.addClass('new-day').removeClass('sequential');
+		} else if (previousNode.dataset) {
+			const previousDataset = previousNode.dataset;
+			const previousMessageDate = new Date(parseInt(previousDataset.timestamp));
+			const currentMessageDate = new Date(parseInt(currentDataset.timestamp));
+			if (previousMessageDate.toDateString() !== currentMessageDate.toDateString()) {
+				$currentNode.addClass('new-day').removeClass('sequential');
+			} else {
+				$currentNode.removeClass('new-day');
+			}
+			if (previousDataset.groupable === 'false' || currentDataset.groupable === 'false') {
+				$currentNode.removeClass('sequential');
+			} else if (previousDataset.username !== currentDataset.username || parseInt(currentDataset.timestamp) - parseInt(previousDataset.timestamp) > RocketChat.settings.get('Message_GroupingPeriod') * 1000) {
+				$currentNode.removeClass('sequential');
+			} else if (!$currentNode.hasClass('new-day')) {
+				$currentNode.addClass('sequential');
+			}
+		}
+		if (nextNode && nextNode.dataset) {
+			const nextDataset = nextNode.dataset;
+			if (nextDataset.date !== currentDataset.date) {
+				$nextNode.addClass('new-day').removeClass('sequential');
+			} else {
+				$nextNode.removeClass('new-day');
+			}
+			if (nextDataset.groupable !== 'false') {
+				if (nextDataset.username !== currentDataset.username || parseInt(nextDataset.timestamp) - parseInt(currentDataset.timestamp) > RocketChat.settings.get('Message_GroupingPeriod') * 1000) {
+					$nextNode.removeClass('sequential');
+				} else if (!$nextNode.hasClass('new-day')) {
+					$nextNode.addClass('sequential');
+				}
+			}
+		}
+		if (nextNode == null) {
+			const [el] = $(`#chat-window-${ context.rid }`);
+			const view = el && Blaze.getView(el);
+			const templateInstance = view && view.templateInstance();
+			if (!templateInstance) {
+				return;
+			}
+			if (currentNode.classList.contains('own') === true) {
+				return (templateInstance.atBottom = true);
+			} else if (templateInstance.firstNode && templateInstance.atBottom === false) {
+				const newMessage = templateInstance.find('.new-message');
+				return newMessage && (newMessage.className = 'new-message background-primary-action-color color-content-background-color ');
+			}
+		}
+	});
+};
diff --git a/packages/rocketchat-ui-message/client/messageBox.coffee b/packages/rocketchat-ui-message/client/messageBox.coffee
deleted file mode 100644
index 2587fda0c301ecbd128e41861c4d0fd4e683bed1..0000000000000000000000000000000000000000
--- a/packages/rocketchat-ui-message/client/messageBox.coffee
+++ /dev/null
@@ -1,391 +0,0 @@
-import toastr from 'toastr'
-import mime from 'mime-type/with-db'
-import moment from 'moment'
-import {VRecDialog} from 'meteor/rocketchat:ui-vrecord'
-
-katexSyntax = ->
-	if RocketChat.katex.katex_enabled()
-		return "$$KaTeX$$"   if RocketChat.katex.dollar_syntax_enabled()
-		return "\\[KaTeX\\]" if RocketChat.katex.parenthesis_syntax_enabled()
-
-	return false
-
-Template.messageBox.helpers
-	roomName: ->
-		roomData = Session.get('roomData' + this._id)
-		return '' unless roomData
-
-		if roomData.t is 'd'
-			return ChatSubscription.findOne({ rid: this._id }, { fields: { name: 1 } })?.name
-		else
-			return roomData.name
-	showMarkdown: ->
-		return RocketChat.Markdown
-	showMarkdownCode: ->
-		return RocketChat.MarkdownCode
-	showKatex: ->
-		return RocketChat.katex
-	katexSyntax: ->
-		return katexSyntax()
-	showFormattingTips: ->
-		return RocketChat.settings.get('Message_ShowFormattingTips') and (RocketChat.Markdown or RocketChat.MarkdownCode or katexSyntax())
-	canJoin: ->
-		return Meteor.userId()? and RocketChat.roomTypes.verifyShowJoinLink @_id
-	joinCodeRequired: ->
-		return Session.get('roomData' + this._id)?.joinCodeRequired
-	subscribed: ->
-		return RocketChat.roomTypes.verifyCanSendMessage @_id
-	allowedToSend: ->
-		if RocketChat.roomTypes.readOnly @_id, Meteor.user()
-			return false
-
-		if RocketChat.roomTypes.archived @_id
-			return false
-
-		roomData = Session.get('roomData' + this._id)
-		if roomData?.t is 'd'
-			subscription = ChatSubscription.findOne({ rid: this._id }, { fields: { archived: 1, blocked: 1, blocker: 1 } })
-			if subscription and (subscription.archived or subscription.blocked or subscription.blocker)
-				return false
-
-		return true
-	isBlockedOrBlocker: ->
-		roomData = Session.get('roomData' + this._id)
-		if roomData?.t is 'd'
-			subscription = ChatSubscription.findOne({ rid: this._id }, { fields: { blocked: 1, blocker: 1 } })
-			if subscription and (subscription.blocked or subscription.blocker)
-				return true
-
-	getPopupConfig: ->
-		template = Template.instance()
-		return {
-			getInput: ->
-				return template.find('.input-message')
-		}
-	usersTyping: ->
-		users = MsgTyping.get @_id
-		if users.length is 0
-			return
-		if users.length is 1
-			return {
-				multi: false
-				selfTyping: MsgTyping.selfTyping.get()
-				users: users[0]
-			}
-		# usernames = _.map messages, (message) -> return message.u.username
-		last = users.pop()
-		if users.length > 4
-			last = t('others')
-		# else
-		usernames = users.join(', ')
-		usernames = [usernames, last]
-		return {
-			multi: true
-			selfTyping: MsgTyping.selfTyping.get()
-			users: usernames.join " #{t 'and'} "
-		}
-
-	groupAttachHidden: ->
-		return 'hidden' if RocketChat.settings.get('Message_Attachments_GroupAttach')
-
-	fileUploadEnabled: ->
-		return RocketChat.settings.get('FileUpload_Enabled')
-
-	fileUploadAllowedMediaTypes: ->
-		return RocketChat.settings.get('FileUpload_MediaTypeWhiteList')
-
-	showFileUpload: ->
-		if (RocketChat.settings.get('FileUpload_Enabled'))
-			roomData = Session.get('roomData' + this._id)
-			if roomData?.t is 'd'
-				return RocketChat.settings.get('FileUpload_Enabled_Direct')
-			else
-				return true
-		else
-			return RocketChat.settings.get('FileUpload_Enabled')
-
-
-	showMic: ->
-		return Template.instance().showMicButton.get()
-
-	showVRec: ->
-		return Template.instance().showVideoRec.get()
-
-	showSend: ->
-		if not Template.instance().isMessageFieldEmpty.get()
-			return 'show-send'
-
-	showLocation: ->
-		return RocketChat.Geolocation.get() isnt false
-
-	notSubscribedTpl: ->
-		return RocketChat.roomTypes.getNotSubscribedTpl @_id
-
-	showSandstorm: ->
-		return Meteor.settings.public.sandstorm && !Meteor.isCordova
-
-	anonymousRead: ->
-		return not Meteor.userId()? and RocketChat.settings.get('Accounts_AllowAnonymousRead') is true
-
-	anonymousWrite: ->
-		return not Meteor.userId()? and RocketChat.settings.get('Accounts_AllowAnonymousRead') is true and RocketChat.settings.get('Accounts_AllowAnonymousWrite') is true
-
-firefoxPasteUpload = (fn) ->
-	user = navigator.userAgent.match(/Firefox\/(\d+)\.\d/)
-	if !user or user[1] > 49
-		return fn
-	return (event, instance) ->
-		if (event.originalEvent.ctrlKey or event.originalEvent.metaKey) and (event.keyCode == 86)
-			textarea = instance.find("textarea")
-			selectionStart = textarea.selectionStart
-			selectionEnd = textarea.selectionEnd
-			contentEditableDiv = instance.find('#msg_contenteditable')
-			contentEditableDiv.focus()
-			Meteor.setTimeout ->
-				pastedImg = contentEditableDiv.querySelector 'img'
-				textareaContent = textarea.value
-				startContent = textareaContent.substring(0, selectionStart)
-				endContent = textareaContent.substring(selectionEnd)
-				restoreSelection = (pastedText) ->
-					textarea.value = startContent + pastedText + endContent
-					textarea.selectionStart = selectionStart + pastedText.length
-					textarea.selectionEnd = textarea.selectionStart
-				contentEditableDiv.innerHTML = '' if pastedImg
-				textarea.focus
-				return if (!pastedImg || contentEditableDiv.innerHTML.length > 0)
-					[].slice.call(contentEditableDiv.querySelectorAll("br")).forEach (el) ->
-						contentEditableDiv.replaceChild(new Text("\n") , el)
-						restoreSelection(contentEditableDiv.innerText)
-				imageSrc = pastedImg.getAttribute("src")
-				if imageSrc.match(/^data:image/)
-					fetch(imageSrc)
-					.then((img)->
-						return img.blob())
-					.then (blob)->
-						fileUpload [{
-							file: blob
-							name: 'Clipboard'
-						}]
-			, 150
-		fn?.apply @, arguments
-
-
-Template.messageBox.events
-	'click .join': (event) ->
-		event.stopPropagation()
-		event.preventDefault()
-		Meteor.call 'joinRoom', @_id, Template.instance().$('[name=joinCode]').val(), (err) =>
-			if err?
-				toastr.error t(err.reason)
-
-			if RocketChat.authz.hasAllPermission('preview-c-room') is false and RoomHistoryManager.getRoom(@_id).loaded is 0
-				RoomManager.getOpenedRoomByRid(@_id).streamActive = false
-				RoomManager.getOpenedRoomByRid(@_id).ready = false
-				RoomHistoryManager.getRoom(@_id).loaded = undefined
-				RoomManager.computation.invalidate()
-
-	'click .register': (event) ->
-		event.stopPropagation()
-		event.preventDefault()
-		Session.set('forceLogin', true)
-
-	'click .register-anonymous': (event) ->
-		event.stopPropagation()
-		event.preventDefault()
-
-		Meteor.call 'registerUser', {}, (error, loginData) ->
-			if loginData && loginData.token
-				Meteor.loginWithToken loginData.token
-
-
-	'focus .input-message': (event, instance) ->
-		KonchatNotification.removeRoomNotification @_id
-		chatMessages[@_id].input = instance.find('.input-message')
-
-	'click .send-button': (event, instance) ->
-		input = instance.find('.input-message')
-		chatMessages[@_id].send(@_id, input, =>
-			# fixes https://github.com/RocketChat/Rocket.Chat/issues/3037
-			# at this point, the input is cleared and ready for autogrow
-			input.updateAutogrow()
-			instance.isMessageFieldEmpty.set(chatMessages[@_id].isEmpty())
-		)
-		input.focus()
-
-	'keyup .input-message': (event, instance) ->
-		chatMessages[@_id].keyup(@_id, event, instance)
-		instance.isMessageFieldEmpty.set(chatMessages[@_id].isEmpty())
-
-	'paste .input-message': (e, instance) ->
-		Meteor.setTimeout ->
-			input = instance.find('.input-message')
-			input.updateAutogrow?()
-		, 50
-
-		if not e.originalEvent.clipboardData?
-			return
-		items = e.originalEvent.clipboardData.items
-		files = []
-		for item in items
-			if item.kind is 'file' and item.type.indexOf('image/') isnt -1
-				e.preventDefault()
-				files.push
-					file: item.getAsFile()
-					name: 'Clipboard - ' + moment().format(RocketChat.settings.get('Message_TimeAndDateFormat'))
-
-		if files.length
-			fileUpload files
-		else
-			instance.isMessageFieldEmpty.set(false)
-
-	'keydown .input-message': firefoxPasteUpload((event, instance) ->
-		chatMessages[@_id].keydown(@_id, event, Template.instance()))
-
-	'input .input-message': (event) ->
-		chatMessages[@_id].valueChanged(@_id, event, Template.instance())
-
-	'propertychange .input-message': (event) ->
-		if event.originalEvent.propertyName is 'value'
-			chatMessages[@_id].valueChanged(@_id, event, Template.instance())
-
-	"click .editing-commands-cancel > button": (e) ->
-		chatMessages[@_id].clearEditing()
-
-	"click .editing-commands-save > button": (e) ->
-		chatMessages[@_id].send(@_id, chatMessages[@_id].input)
-
-	'change .message-form input[type=file]': (event, template) ->
-		e = event.originalEvent or event
-		files = e.target.files
-		if not files or files.length is 0
-			files = e.dataTransfer?.files or []
-
-		filesToUpload = []
-		for file in files
-			# `file.type = mime.lookup(file.name)` does not work.
-			Object.defineProperty(file, 'type', { value: mime.lookup(file.name) })
-			filesToUpload.push
-				file: file
-				name: file.name
-
-		fileUpload filesToUpload
-
-	"click .message-buttons.share": (e, t) ->
-		t.$('.share-items').toggleClass('hidden')
-		t.$('.message-buttons.share').toggleClass('active')
-
-	'click .message-form .message-buttons.location': (event, instance) ->
-		roomId = @_id
-
-		position = RocketChat.Geolocation.get()
-
-		latitude = position.coords.latitude
-		longitude = position.coords.longitude
-
-		text = """
-			<div class="location-preview">
-				<img style="height: 250px; width: 250px;" src="https://maps.googleapis.com/maps/api/staticmap?zoom=14&size=250x250&markers=color:gray%7Clabel:%7C#{latitude},#{longitude}&key=#{RocketChat.settings.get('MapView_GMapsAPIKey')}" />
-			</div>
-		"""
-
-		swal
-			title: t('Share_Location_Title')
-			text: text
-			showCancelButton: true
-			closeOnConfirm: true
-			closeOnCancel: true
-			html: true
-		, (isConfirm) ->
-			if isConfirm isnt true
-				return
-
-			Meteor.call "sendMessage",
-				_id: Random.id()
-				rid: roomId
-				msg: ""
-				location:
-					type: 'Point'
-					coordinates: [ longitude, latitude ]
-
-
-	'click .message-form .mic': (e, t) ->
-		AudioRecorder.start ->
-			t.$('.stop-mic').removeClass('hidden')
-			t.$('.mic').addClass('hidden')
-
-	'click .message-form .video-button': (e, t) ->
-		if VRecDialog.opened
-			VRecDialog.close()
-		else
-			VRecDialog.open(e.currentTarget)
-
-	'click .message-form .stop-mic': (e, t) ->
-		AudioRecorder.stop (blob) ->
-			fileUpload [{
-				file: blob
-				type: 'audio'
-				name: TAPi18n.__('Audio record') + '.wav'
-			}]
-
-		t.$('.stop-mic').addClass('hidden')
-		t.$('.mic').removeClass('hidden')
-
-	'click .sandstorm-offer': (e, t) ->
-		roomId = @_id
-		RocketChat.Sandstorm.request "uiView", (err, data) =>
-			if err or !data.token
-				console.error err
-				return
-			Meteor.call "sandstormClaimRequest", data.token, data.descriptor, (err, viewInfo) =>
-				if err
-					console.error err
-					return
-
-				Meteor.call "sendMessage", {
-					_id: Random.id()
-					rid: roomId
-					msg: ""
-					urls: [{ url: "grain://sandstorm", sandstormViewInfo: viewInfo }]
-				}
-
-Template.messageBox.onCreated ->
-	@isMessageFieldEmpty = new ReactiveVar true
-	@showMicButton = new ReactiveVar false
-	@showVideoRec = new ReactiveVar false
-
-	@autorun =>
-		videoRegex = /video\/webm|video\/\*/i
-		videoEnabled = !RocketChat.settings.get("FileUpload_MediaTypeWhiteList") || RocketChat.settings.get("FileUpload_MediaTypeWhiteList").match(videoRegex)
-		if RocketChat.settings.get('Message_VideoRecorderEnabled') and (navigator.getUserMedia? or navigator.webkitGetUserMedia?) and videoEnabled and RocketChat.settings.get('FileUpload_Enabled')
-			@showVideoRec.set true
-		else
-			@showVideoRec.set false
-
-		wavRegex = /audio\/wav|audio\/\*/i
-		wavEnabled = !RocketChat.settings.get("FileUpload_MediaTypeWhiteList") || RocketChat.settings.get("FileUpload_MediaTypeWhiteList").match(wavRegex)
-		if RocketChat.settings.get('Message_AudioRecorderEnabled') and (navigator.getUserMedia? or navigator.webkitGetUserMedia?) and wavEnabled and RocketChat.settings.get('FileUpload_Enabled')
-			@showMicButton.set true
-		else
-			@showMicButton.set false
-
-
-Meteor.startup ->
-	RocketChat.Geolocation = new ReactiveVar false
-
-	Tracker.autorun ->
-		if RocketChat.settings.get('MapView_Enabled') is true and RocketChat.settings.get('MapView_GMapsAPIKey')?.length and navigator.geolocation?.getCurrentPosition?
-			success = (position) =>
-				RocketChat.Geolocation.set position
-
-			error = (error) =>
-				console.log 'Error getting your geolocation', error
-				RocketChat.Geolocation.set false
-
-			options =
-				enableHighAccuracy: true
-				maximumAge: 0
-				timeout: 10000
-
-			navigator.geolocation.watchPosition success, error
-		else
-			RocketChat.Geolocation.set false
diff --git a/packages/rocketchat-ui-message/client/messageBox.js b/packages/rocketchat-ui-message/client/messageBox.js
new file mode 100644
index 0000000000000000000000000000000000000000..add30a122142c3afacbf892c932821e54e346d0a
--- /dev/null
+++ b/packages/rocketchat-ui-message/client/messageBox.js
@@ -0,0 +1,454 @@
+/* globals fileUpload AudioRecorder KonchatNotification chatMessages */
+import toastr from 'toastr';
+
+import mime from 'mime-type/with-db';
+
+import moment from 'moment';
+
+import {VRecDialog} from 'meteor/rocketchat:ui-vrecord';
+
+function katexSyntax() {
+	if (RocketChat.katex.katex_enabled()) {
+		if (RocketChat.katex.dollar_syntax_enabled()) {
+			return '$$KaTeX$$';
+		}
+		if (RocketChat.katex.parenthesis_syntax_enabled()) {
+			return '\\[KaTeX\\]';
+		}
+	}
+	return false;
+}
+
+Template.messageBox.helpers({
+	roomName() {
+		const roomData = Session.get(`roomData${ this._id }`);
+		if (!roomData) {
+			return '';
+		}
+		if (roomData.t === 'd') {
+			const chat = ChatSubscription.findOne({
+				rid: this._id
+			}, {
+				fields: {
+					name: 1
+				}
+			});
+			return chat && chat.name;
+		} else {
+			return roomData.name;
+		}
+	},
+	showMarkdown() {
+		return RocketChat.Markdown;
+	},
+	showMarkdownCode() {
+		return RocketChat.MarkdownCode;
+	},
+	showKatex() {
+		return RocketChat.katex;
+	},
+	katexSyntax() {
+		return katexSyntax();
+	},
+	showFormattingTips() {
+		return RocketChat.settings.get('Message_ShowFormattingTips') && (RocketChat.Markdown || RocketChat.MarkdownCode || katexSyntax());
+	},
+	canJoin() {
+		return RocketChat.roomTypes.verifyShowJoinLink(this._id);
+	},
+	joinCodeRequired() {
+		const code = Session.get(`roomData${ this._id }`);
+		return code && code.joinCodeRequired;
+	},
+	subscribed() {
+		return RocketChat.roomTypes.verifyCanSendMessage(this._id);
+	},
+	allowedToSend() {
+		if (RocketChat.roomTypes.readOnly(this._id, Meteor.user())) {
+			return false;
+		}
+		if (RocketChat.roomTypes.archived(this._id)) {
+			return false;
+		}
+		const roomData = Session.get(`roomData${ this._id }`);
+		if (roomData && roomData.t === 'd') {
+			const subscription = ChatSubscription.findOne({
+				rid: this._id
+			}, {
+				fields: {
+					archived: 1,
+					blocked: 1,
+					blocker: 1
+				}
+			});
+			if (subscription && (subscription.archived || subscription.blocked || subscription.blocker)) {
+				return false;
+			}
+		}
+		return true;
+	},
+	isBlockedOrBlocker() {
+		const roomData = Session.get(`roomData${ this._id }`);
+		if (roomData && roomData.t === 'd') {
+			const subscription = ChatSubscription.findOne({
+				rid: this._id
+			}, {
+				fields: {
+					blocked: 1,
+					blocker: 1
+				}
+			});
+			if (subscription && (subscription.blocked || subscription.blocker)) {
+				return true;
+			}
+		}
+	},
+	getPopupConfig() {
+		const template = Template.instance();
+		return {
+			getInput() {
+				return template.find('.input-message');
+			}
+		};
+	},
+	/* globals MsgTyping*/
+	usersTyping() {
+		const users = MsgTyping.get(this._id);
+		if (users.length === 0) {
+			return;
+		}
+		if (users.length === 1) {
+			return {
+				multi: false,
+				selfTyping: MsgTyping.selfTyping.get(),
+				users: users[0]
+			};
+		}
+		let last = users.pop();
+		if (users.length > 4) {
+			last = t('others');
+		}
+		let usernames = users.join(', ');
+		usernames = [usernames, last];
+		return {
+			multi: true,
+			selfTyping: MsgTyping.selfTyping.get(),
+			users: usernames.join(` ${ t('and') } `)
+		};
+	},
+	groupAttachHidden() {
+		if (RocketChat.settings.get('Message_Attachments_GroupAttach')) {
+			return 'hidden';
+		}
+	},
+	fileUploadEnabled() {
+		return RocketChat.settings.get('FileUpload_Enabled');
+	},
+	fileUploadAllowedMediaTypes() {
+		return RocketChat.settings.get('FileUpload_MediaTypeWhiteList');
+	},
+	showFileUpload() {
+		let roomData;
+		if (RocketChat.settings.get('FileUpload_Enabled')) {
+			roomData = Session.get(`roomData${ this._id }`);
+			if (roomData && roomData.t === 'd') {
+				return RocketChat.settings.get('FileUpload_Enabled_Direct');
+			} else {
+				return true;
+			}
+		} else {
+			return RocketChat.settings.get('FileUpload_Enabled');
+		}
+	},
+	showMic() {
+		return Template.instance().showMicButton.get();
+	},
+	showVRec() {
+		return Template.instance().showVideoRec.get();
+	},
+	showSend() {
+		if (!Template.instance().isMessageFieldEmpty.get()) {
+			return 'show-send';
+		}
+	},
+	showLocation() {
+		return RocketChat.Geolocation.get() !== false;
+	},
+	notSubscribedTpl() {
+		return RocketChat.roomTypes.getNotSubscribedTpl(this._id);
+	},
+	showSandstorm() {
+		return Meteor.settings['public'].sandstorm && !Meteor.isCordova;
+	}
+});
+
+function firefoxPasteUpload(fn) {
+	const user = navigator.userAgent.match(/Firefox\/(\d+)\.\d/);
+	if (!user || user[1] > 49) {
+		return fn;
+	}
+	return function(event, instance) {
+		if ((event.originalEvent.ctrlKey || event.originalEvent.metaKey) && (event.keyCode === 86)) {
+			const textarea = instance.find('textarea');
+			const {selectionStart, selectionEnd} = textarea;
+			const contentEditableDiv = instance.find('#msg_contenteditable');
+			contentEditableDiv.focus();
+			Meteor.setTimeout(function() {
+				const pastedImg = contentEditableDiv.querySelector('img');
+				const textareaContent = textarea.value;
+				const startContent = textareaContent.substring(0, selectionStart);
+				const endContent = textareaContent.substring(selectionEnd);
+				const restoreSelection = function(pastedText) {
+					textarea.value = startContent + pastedText + endContent;
+					textarea.selectionStart = selectionStart + pastedText.length;
+					return textarea.selectionEnd = textarea.selectionStart;
+				};
+				if (pastedImg) {
+					contentEditableDiv.innerHTML = '';
+				}
+				textarea.focus;
+				if (!pastedImg || contentEditableDiv.innerHTML.length > 0) {
+					return [].slice.call(contentEditableDiv.querySelectorAll('br')).forEach(function(el) {
+						contentEditableDiv.replaceChild(new Text('\n'), el);
+						return restoreSelection(contentEditableDiv.innerText);
+					});
+				}
+				const imageSrc = pastedImg.getAttribute('src');
+				if (imageSrc.match(/^data:image/)) {
+					return fetch(imageSrc).then(function(img) {
+						return img.blob();
+					}).then(function(blob) {
+						return fileUpload([
+							{
+								file: blob,
+								name: 'Clipboard'
+							}
+						]);
+					});
+				}
+			}, 150);
+		}
+		return fn && fn.apply(this, arguments);
+	};
+}
+
+Template.messageBox.events({
+	'click .join'(event) {
+		event.stopPropagation();
+		event.preventDefault();
+		Meteor.call('joinRoom', this._id, Template.instance().$('[name=joinCode]').val(), (err) => {
+			if (err != null) {
+				toastr.error(t(err.reason));
+			}
+			if (RocketChat.authz.hasAllPermission('preview-c-room') === false && RoomHistoryManager.getRoom(this._id).loaded === 0) {
+				RoomManager.getOpenedRoomByRid(this._id).streamActive = false;
+				RoomManager.getOpenedRoomByRid(this._id).ready = false;
+				RoomHistoryManager.getRoom(this._id).loaded = null;
+				RoomManager.computation.invalidate();
+			}
+		});
+	},
+	'focus .input-message'(event, instance) {
+		KonchatNotification.removeRoomNotification(this._id);
+		chatMessages[this._id].input = instance.find('.input-message');
+	},
+	'click .send-button'(event, instance) {
+		const input = instance.find('.input-message');
+		chatMessages[this._id].send(this._id, input, () => {
+			// fixes https://github.com/RocketChat/Rocket.Chat/issues/3037
+			// at this point, the input is cleared and ready for autogrow
+			input.updateAutogrow();
+			return instance.isMessageFieldEmpty.set(chatMessages[this._id].isEmpty());
+		});
+		return input.focus();
+	},
+	'keyup .input-message'(event, instance) {
+		chatMessages[this._id].keyup(this._id, event, instance);
+		return instance.isMessageFieldEmpty.set(chatMessages[this._id].isEmpty());
+	},
+	'paste .input-message'(e, instance) {
+		Meteor.setTimeout(function() {
+			const input = instance.find('.input-message');
+			return typeof input.updateAutogrow === 'function' && input.updateAutogrow();
+		}, 50);
+		if (e.originalEvent.clipboardData == null) {
+			return;
+		}
+		const items = [...e.originalEvent.clipboardData.items];
+		const files = items.map(item => {
+			if (item.kind === 'file' && item.type.indexOf('image/') !== -1) {
+				e.preventDefault();
+				return {
+					file: item.getAsFile(),
+					name: `Clipboard - ${ moment().format(RocketChat.settings.get('Message_TimeAndDateFormat')) }`
+				};
+			}
+		}).filter(e => e);
+		if (files.length) {
+			return fileUpload(files);
+		} else {
+			return instance.isMessageFieldEmpty.set(false);
+		}
+	},
+	'keydown .input-message': firefoxPasteUpload(function(event) {
+		return chatMessages[this._id].keydown(this._id, event, Template.instance());
+	}),
+	'input .input-message'(event) {
+		return chatMessages[this._id].valueChanged(this._id, event, Template.instance());
+	},
+	'propertychange .input-message'(event) {
+		if (event.originalEvent.propertyName === 'value') {
+			return chatMessages[this._id].valueChanged(this._id, event, Template.instance());
+		}
+	},
+	'click .editing-commands-cancel > button'() {
+		return chatMessages[this._id].clearEditing();
+	},
+	'click .editing-commands-save > button'() {
+		return chatMessages[this._id].send(this._id, chatMessages[this._id].input);
+	},
+	'change .message-form input[type=file]'(event) {
+		const e = event.originalEvent || event;
+		let files = e.target.files;
+		if (!files || files.length === 0) {
+			files = (e.dataTransfer && e.dataTransfer.files) || [];
+		}
+		const filesToUpload = [...files].map(file => {
+			// `file.type = mime.lookup(file.name)` does not work.
+			Object.defineProperty(file, 'type', {
+				value: mime.lookup(file.name)
+			});
+			return {
+				file,
+				name: file.name
+			};
+		});
+		return fileUpload(filesToUpload);
+	},
+	'click .message-buttons.share'(e, t) {
+		t.$('.share-items').toggleClass('hidden');
+		return t.$('.message-buttons.share').toggleClass('active');
+	},
+	'click .message-form .message-buttons.location'() {
+		const roomId = this._id;
+		const position = RocketChat.Geolocation.get();
+		const latitude = position.coords.latitude;
+		const longitude = position.coords.longitude;
+		const text = `<div class="location-preview">\n	<img style="height: 250px; width: 250px;" src="https://maps.googleapis.com/maps/api/staticmap?zoom=14&size=250x250&markers=color:gray%7Clabel:%7C${ latitude },${ longitude }&key=${ RocketChat.settings.get('MapView_GMapsAPIKey') }" />\n</div>`;
+		return swal({
+			title: t('Share_Location_Title'),
+			text,
+			showCancelButton: true,
+			closeOnConfirm: true,
+			closeOnCancel: true,
+			html: true
+		}, function(isConfirm) {
+			if (isConfirm !== true) {
+				return;
+			}
+			return Meteor.call('sendMessage', {
+				_id: Random.id(),
+				rid: roomId,
+				msg: '',
+				location: {
+					type: 'Point',
+					coordinates: [longitude, latitude]
+				}
+			});
+		});
+	},
+	'click .message-form .mic'(e, t) {
+		return AudioRecorder.start(function() {
+			t.$('.stop-mic').removeClass('hidden');
+			return t.$('.mic').addClass('hidden');
+		});
+	},
+	'click .message-form .video-button'(e) {
+		return VRecDialog.opened ? VRecDialog.close() : VRecDialog.open(e.currentTarget);
+	},
+	'click .message-form .stop-mic'(e, t) {
+		AudioRecorder.stop(function(blob) {
+			return fileUpload([
+				{
+					file: blob,
+					type: 'audio',
+					name: `${ TAPi18n.__('Audio record') }.wav`
+				}
+			]);
+		});
+		t.$('.stop-mic').addClass('hidden');
+		return t.$('.mic').removeClass('hidden');
+	},
+	'click .sandstorm-offer'() {
+		const roomId = this._id;
+		return RocketChat.Sandstorm.request('uiView', (err, data) => {
+			if (err || !data.token) {
+				console.error(err);
+				return;
+			}
+			return Meteor.call('sandstormClaimRequest', data.token, data.descriptor, function(err, viewInfo) {
+				if (err) {
+					console.error(err);
+					return;
+				}
+				Meteor.call('sendMessage', {
+					_id: Random.id(),
+					rid: roomId,
+					msg: '',
+					urls: [
+						{
+							url: 'grain://sandstorm',
+							sandstormViewInfo: viewInfo
+						}
+					]
+				});
+			});
+		});
+	}
+});
+
+Template.messageBox.onCreated(function() {
+	this.isMessageFieldEmpty = new ReactiveVar(true);
+	this.showMicButton = new ReactiveVar(false);
+	this.showVideoRec = new ReactiveVar(false);
+	return this.autorun(() => {
+		const videoRegex = /video\/webm|video\/\*/i;
+		const videoEnabled = !RocketChat.settings.get('FileUpload_MediaTypeWhiteList') || RocketChat.settings.get('FileUpload_MediaTypeWhiteList').match(videoRegex);
+		if (RocketChat.settings.get('Message_VideoRecorderEnabled') && ((navigator.getUserMedia != null) || (navigator.webkitGetUserMedia != null)) && videoEnabled && RocketChat.settings.get('FileUpload_Enabled')) {
+			this.showVideoRec.set(true);
+		} else {
+			this.showVideoRec.set(false);
+		}
+		const wavRegex = /audio\/wav|audio\/\*/i;
+		const wavEnabled = !RocketChat.settings.get('FileUpload_MediaTypeWhiteList') || RocketChat.settings.get('FileUpload_MediaTypeWhiteList').match(wavRegex);
+		if (RocketChat.settings.get('Message_AudioRecorderEnabled') && ((navigator.getUserMedia != null) || (navigator.webkitGetUserMedia != null)) && wavEnabled && RocketChat.settings.get('FileUpload_Enabled')) {
+			return this.showMicButton.set(true);
+		} else {
+			return this.showMicButton.set(false);
+		}
+	});
+});
+
+Meteor.startup(function() {
+	RocketChat.Geolocation = new ReactiveVar(false);
+	return Tracker.autorun(function() {
+		const MapView_GMapsAPIKey = RocketChat.settings.get('MapView_GMapsAPIKey');
+		if (RocketChat.settings.get('MapView_Enabled') === true && MapView_GMapsAPIKey && MapView_GMapsAPIKey.length && navigator.geolocation && navigator.geolocation.getCurrentPosition) {
+			const success = (position) => {
+				return RocketChat.Geolocation.set(position);
+			};
+			const error = (error) => {
+				console.log('Error getting your geolocation', error);
+				return RocketChat.Geolocation.set(false);
+			};
+			const options = {
+				enableHighAccuracy: true,
+				maximumAge: 0,
+				timeout: 10000
+			};
+			return navigator.geolocation.watchPosition(success, error, options);
+		} else {
+			return RocketChat.Geolocation.set(false);
+		}
+	});
+});
diff --git a/packages/rocketchat-ui-message/client/popup/messagePopup.coffee b/packages/rocketchat-ui-message/client/popup/messagePopup.coffee
deleted file mode 100644
index c76fcf1b1711fe501d76781c1c41d858518bbecd..0000000000000000000000000000000000000000
--- a/packages/rocketchat-ui-message/client/popup/messagePopup.coffee
+++ /dev/null
@@ -1,282 +0,0 @@
-# This is not supposed to be a complete list
-# it is just to improve readability in this file
-keys = {
-	TAB: 9
-	ENTER: 13
-	ESC: 27
-	ARROW_LEFT: 37
-	ARROW_UP: 38
-	ARROW_RIGHT: 39
-	ARROW_DOWN: 40
-}
-
-getCursorPosition = (input) ->
-	if not input? then return
-	if input.selectionStart?
-		return input.selectionStart
-	else if document.selection?
-		input.focus()
-		sel = document.selection.createRange()
-		selLen = document.selection.createRange().text.length
-		sel.moveStart('character', - input.value.length)
-		return sel.text.length - selLen
-
-setCursorPosition = (input, caretPos) ->
-	if not input? then return
-	if input.selectionStart?
-		input.focus()
-		return input.setSelectionRange(caretPos, caretPos)
-	else if document.selection?
-		range = input.createTextRange()
-		range.move('character', caretPos)
-		range.select()
-
-val = (v, d) ->
-	return if v? then v else d
-
-Template.messagePopup.onCreated ->
-	template = this
-
-	template.textFilter = new ReactiveVar ''
-
-	template.textFilterDelay = val(template.data.textFilterDelay, 0)
-
-	template.open = val(template.data.open, new ReactiveVar(false))
-
-	template.hasData = new ReactiveVar false
-
-	template.value = new ReactiveVar
-
-	template.trigger = val(template.data.trigger, '')
-
-	template.triggerAnywhere = val(template.data.triggerAnywhere, true)
-
-	template.closeOnEsc = val(template.data.closeOnEsc, true)
-
-	template.blurOnSelectItem = val(template.data.blurOnSelectItem, false)
-
-	template.prefix = val(template.data.prefix, template.trigger)
-
-	template.suffix = val(template.data.suffix, '')
-
-	if template.triggerAnywhere is true
-		template.matchSelectorRegex = val(template.data.matchSelectorRegex, new RegExp "(?:^| )#{template.trigger}[^\\s]*$")
-	else
-		template.matchSelectorRegex = val(template.data.matchSelectorRegex, new RegExp "(?:^)#{template.trigger}[^\\s]*$")
-
-	template.selectorRegex = val(template.data.selectorRegex, new RegExp "#{template.trigger}([^\\s]*)$")
-
-	template.replaceRegex = val(template.data.replaceRegex, new RegExp "#{template.trigger}[^\\s]*$")
-
-	template.getValue = val template.data.getValue, (_id) -> return _id
-
-	template.up = =>
-		current = template.find('.popup-item.selected')
-		previous = $(current).prev('.popup-item')[0] or template.find('.popup-item:last-child')
-		if previous?
-			current.className = current.className.replace /\sselected/, ''
-			previous.className += ' selected'
-			template.value.set previous.getAttribute('data-id')
-
-	template.down = =>
-		current = template.find('.popup-item.selected')
-		next = $(current).next('.popup-item')[0] or template.find('.popup-item')
-		if next?.classList.contains('popup-item')
-			current.className = current.className.replace /\sselected/, ''
-			next.className += ' selected'
-			template.value.set next.getAttribute('data-id')
-
-	template.verifySelection = =>
-		current = template.find('.popup-item.selected')
-		if not current?
-			first = template.find('.popup-item')
-			if first?
-				first.className += ' selected'
-				template.value.set first.getAttribute('data-id')
-			else
-				template.value.set undefined
-
-	template.onInputKeydown = (event) =>
-		if template.open.curValue isnt true or template.hasData.curValue isnt true
-			return
-
-		if event.which in [keys.ENTER, keys.TAB]
-			if template.blurOnSelectItem is true
-				template.input.blur()
-			else
-				template.open.set false
-
-			template.enterValue()
-
-			if template.data.cleanOnEnter
-				template.input.value = ''
-
-			event.preventDefault()
-			event.stopPropagation()
-			return
-
-		if event.which is keys.ARROW_UP
-			template.up()
-
-			event.preventDefault()
-			event.stopPropagation()
-			return
-
-		if event.which is keys.ARROW_DOWN
-			template.down()
-
-			event.preventDefault()
-			event.stopPropagation()
-			return
-
-	template.setTextFilter = _.debounce (value) ->
-		template.textFilter.set(value)
-	, template.textFilterDelay
-
-	template.onInputKeyup = (event) =>
-		if template.closeOnEsc is true and template.open.curValue is true and event.which is keys.ESC
-			template.open.set false
-			event.preventDefault()
-			event.stopPropagation()
-			return
-
-		value = template.input.value
-		value = value.substr 0, getCursorPosition(template.input)
-
-		if template.matchSelectorRegex.test value
-			template.setTextFilter value.match(template.selectorRegex)[1]
-			template.open.set true
-		else
-			template.open.set false
-
-		if template.open.curValue isnt true
-			return
-
-		if event.which not in [keys.ARROW_UP, keys.ARROW_DOWN]
-			Meteor.defer =>
-				template.verifySelection()
-
-	template.onFocus = (event) =>
-		template.clickingItem = false;
-
-		if template.open.curValue is true
-			return
-
-		value = template.input.value
-		value = value.substr 0, getCursorPosition(template.input)
-
-		if template.matchSelectorRegex.test value
-			template.setTextFilter value.match(template.selectorRegex)[1]
-			template.open.set true
-			Meteor.defer =>
-				template.verifySelection()
-		else
-			template.open.set false
-
-	template.onBlur = (event) =>
-		if template.open.curValue is false
-			return
-
-		if template.clickingItem is true
-			return
-
-		template.open.set false
-
-	template.enterValue = ->
-		if not template.value.curValue? then return
-
-		value = template.input.value
-		caret = getCursorPosition(template.input)
-		firstPartValue = value.substr 0, caret
-		lastPartValue = value.substr caret
-		getValue = this.getValue(template.value.curValue, template.data.collection, template.records.get(), firstPartValue)
-
-		if not getValue
-			return
-
-		firstPartValue = firstPartValue.replace(template.selectorRegex, template.prefix + getValue + template.suffix)
-
-		template.input.value = firstPartValue + lastPartValue
-
-		setCursorPosition template.input, firstPartValue.length
-
-	template.records = new ReactiveVar []
-	Tracker.autorun ->
-		if template.data.collection.findOne?
-			template.data.collection.find().count()
-
-		filter = template.textFilter.get()
-		if filter?
-			filterCallback = (result) =>
-				template.hasData.set result?.length > 0
-				template.records.set result
-
-				Meteor.defer =>
-					template.verifySelection()
-
-			result = template.data.getFilter(template.data.collection, filter, filterCallback)
-			if result?
-				filterCallback result
-
-
-Template.messagePopup.onRendered ->
-	if this.data.getInput?
-		this.input = this.data.getInput?()
-	else if this.data.input
-		this.input = this.parentTemplate().find(this.data.input)
-
-	if not this.input?
-		console.error 'Input not found for popup'
-
-	$(this.input).on 'keyup', this.onInputKeyup.bind this
-	$(this.input).on 'keydown', this.onInputKeydown.bind this
-	$(this.input).on 'focus', this.onFocus.bind this
-	$(this.input).on 'blur', this.onBlur.bind this
-
-
-Template.messagePopup.onDestroyed ->
-	$(this.input).off 'keyup', this.onInputKeyup
-	$(this.input).off 'keydown', this.onInputKeydown
-	$(this.input).off 'focus', this.onFocus
-	$(this.input).off 'blur', this.onBlur
-
-
-Template.messagePopup.events
-	'mouseenter .popup-item': (e) ->
-		if e.currentTarget.className.indexOf('selected') > -1
-			return
-
-		template = Template.instance()
-
-		current = template.find('.popup-item.selected')
-		if current?
-			current.className = current.className.replace /\sselected/, ''
-		e.currentTarget.className += ' selected'
-		template.value.set this._id
-
-	'mousedown .popup-item, touchstart .popup-item': (e) ->
-		template = Template.instance()
-		template.clickingItem = true;
-
-	'mouseup .popup-item, touchend .popup-item': (e) ->
-		template = Template.instance()
-
-		template.clickingItem = false;
-
-		template.value.set this._id
-
-		template.enterValue()
-
-		template.open.set false
-
-		toolbarSearch.clear();
-
-
-Template.messagePopup.helpers
-	isOpen: ->
-		Template.instance().open.get() and ((Template.instance().hasData.get() or Template.instance().data.emptyTemplate?) or not Template.instance().parentTemplate(1).subscriptionsReady())
-
-	data: ->
-		template = Template.instance()
-
-		return template.records.get()
diff --git a/packages/rocketchat-ui-message/client/popup/messagePopup.js b/packages/rocketchat-ui-message/client/popup/messagePopup.js
new file mode 100644
index 0000000000000000000000000000000000000000..57a160bc13bb6e75e868ea6c4d4c20f2cf324732
--- /dev/null
+++ b/packages/rocketchat-ui-message/client/popup/messagePopup.js
@@ -0,0 +1,285 @@
+/* globals toolbarSearch */
+// This is not supposed to be a complete list
+// it is just to improve readability in this file
+const keys = {
+	TAB: 9,
+	ENTER: 13,
+	ESC: 27,
+	ARROW_LEFT: 37,
+	ARROW_UP: 38,
+	ARROW_RIGHT: 39,
+	ARROW_DOWN: 40
+};
+
+function getCursorPosition(input) {
+	if (input == null) {
+		return;
+	}
+	if (input.selectionStart != null) {
+		return input.selectionStart;
+	} else if (document.selection != null) {
+		input.focus();
+		const sel = document.selection.createRange();
+		const selLen = document.selection.createRange().text.length;
+		sel.moveStart('character', -input.value.length);
+		return sel.text.length - selLen;
+	}
+}
+
+function setCursorPosition(input, caretPos) {
+	if (input == null) {
+		return;
+	}
+	if (input.selectionStart != null) {
+		input.focus();
+		return input.setSelectionRange(caretPos, caretPos);
+	} else if (document.selection != null) {
+		const range = input.createTextRange();
+		range.move('character', caretPos);
+		return range.select();
+	}
+}
+
+function val(v, d) {
+	if (v != null) {
+		return v;
+	} else {
+		return d;
+	}
+}
+
+Template.messagePopup.onCreated(function() {
+	const template = this;
+	template.textFilter = new ReactiveVar('');
+	template.textFilterDelay = val(template.data.textFilterDelay, 0);
+	template.open = val(template.data.open, new ReactiveVar(false));
+	template.hasData = new ReactiveVar(false);
+	template.value = new ReactiveVar;
+	template.trigger = val(template.data.trigger, '');
+	template.triggerAnywhere = val(template.data.triggerAnywhere, true);
+	template.closeOnEsc = val(template.data.closeOnEsc, true);
+	template.blurOnSelectItem = val(template.data.blurOnSelectItem, false);
+	template.prefix = val(template.data.prefix, template.trigger);
+	template.suffix = val(template.data.suffix, '');
+	if (template.triggerAnywhere === true) {
+		template.matchSelectorRegex = val(template.data.matchSelectorRegex, new RegExp(`(?:^| )${ template.trigger }[^\\s]*$`));
+	} else {
+		template.matchSelectorRegex = val(template.data.matchSelectorRegex, new RegExp(`(?:^)${ template.trigger }[^\\s]*$`));
+	}
+	template.selectorRegex = val(template.data.selectorRegex, new RegExp(`${ template.trigger }([^\\s]*)$`));
+	template.replaceRegex = val(template.data.replaceRegex, new RegExp(`${ template.trigger }[^\\s]*$`));
+	template.getValue = val(template.data.getValue, function(_id) {
+		return _id;
+	});
+	template.up = () => {
+		const current = template.find('.popup-item.selected');
+		const previous = $(current).prev('.popup-item')[0] || template.find('.popup-item:last-child');
+		if (previous != null) {
+			current.className = current.className.replace(/\sselected/, '');
+			previous.className += ' selected';
+			return template.value.set(previous.getAttribute('data-id'));
+		}
+	};
+	template.down = () => {
+		const current = template.find('.popup-item.selected');
+		const next = $(current).next('.popup-item')[0] || template.find('.popup-item');
+		if (next && next.classList.contains('popup-item')) {
+			current.className = current.className.replace(/\sselected/, '');
+			next.className += ' selected';
+			return template.value.set(next.getAttribute('data-id'));
+		}
+	};
+	template.verifySelection = () => {
+		const current = template.find('.popup-item.selected');
+		if (current == null) {
+			const first = template.find('.popup-item');
+			if (first != null) {
+				first.className += ' selected';
+				return template.value.set(first.getAttribute('data-id'));
+			} else {
+				return template.value.set(null);
+			}
+		}
+	};
+	template.onInputKeydown = (event) => {
+		if (template.open.curValue !== true || template.hasData.curValue !== true) {
+			return;
+		}
+		if (event.which === keys.ENTER || event.which === keys.TAB) {
+			if (template.blurOnSelectItem === true) {
+				template.input.blur();
+			} else {
+				template.open.set(false);
+			}
+			template.enterValue();
+			if (template.data.cleanOnEnter) {
+				template.input.value = '';
+			}
+			event.preventDefault();
+			event.stopPropagation();
+			return;
+		}
+		if (event.which === keys.ARROW_UP) {
+			template.up();
+			event.preventDefault();
+			event.stopPropagation();
+			return;
+		}
+		if (event.which === keys.ARROW_DOWN) {
+			template.down();
+			event.preventDefault();
+			event.stopPropagation();
+		}
+	};
+
+	template.setTextFilter = _.debounce(function(value) {
+		return template.textFilter.set(value);
+	}, template.textFilterDelay);
+
+	template.onInputKeyup = (event) => {
+		if (template.closeOnEsc === true && template.open.curValue === true && event.which === keys.ESC) {
+			template.open.set(false);
+			event.preventDefault();
+			event.stopPropagation();
+			return;
+		}
+		const value = template.input.value.substr(0, getCursorPosition(template.input));
+
+		if (template.matchSelectorRegex.test(value)) {
+			template.setTextFilter(value.match(template.selectorRegex)[1]);
+			template.open.set(true);
+		} else {
+			template.open.set(false);
+		}
+		if (template.open.curValue !== true) {
+			return;
+		}
+		if (event.which !== keys.ARROW_UP && event.which !== keys.ARROW_DOWN) {
+			return Meteor.defer(function() {
+				template.verifySelection();
+			});
+		}
+	};
+	template.onFocus = () => {
+		template.clickingItem = false;
+		if (template.open.curValue === true) {
+			return;
+		}
+		const value = template.input.value.substr(0, getCursorPosition(template.input));
+		if (template.matchSelectorRegex.test(value)) {
+			template.setTextFilter(value.match(template.selectorRegex)[1]);
+			template.open.set(true);
+			return Meteor.defer(function() {
+				return template.verifySelection();
+			});
+		} else {
+			return template.open.set(false);
+		}
+	};
+
+	template.onBlur = () => {
+		if (template.open.curValue === false) {
+			return;
+		}
+		if (template.clickingItem === true) {
+			return;
+		}
+		return template.open.set(false);
+	};
+
+	template.enterValue = function() {
+		if (template.value.curValue == null) {
+			return;
+		}
+		const value = template.input.value;
+		const caret = getCursorPosition(template.input);
+		let firstPartValue = value.substr(0, caret);
+		const lastPartValue = value.substr(caret);
+		const getValue = this.getValue(template.value.curValue, template.data.collection, template.records.get(), firstPartValue);
+		if (!getValue) {
+			return;
+		}
+		firstPartValue = firstPartValue.replace(template.selectorRegex, template.prefix + getValue + template.suffix);
+		template.input.value = firstPartValue + lastPartValue;
+		return setCursorPosition(template.input, firstPartValue.length);
+	};
+	template.records = new ReactiveVar([]);
+	Tracker.autorun(function() {
+		if (template.data.collection.findOne != null) {
+			template.data.collection.find().count();
+		}
+		const filter = template.textFilter.get();
+		if (filter != null) {
+			const filterCallback = (result) => {
+				template.hasData.set(result && result.length > 0);
+				template.records.set(result);
+				return Meteor.defer(function() {
+					return template.verifySelection();
+				});
+			};
+			const result = template.data.getFilter(template.data.collection, filter, filterCallback);
+			if (result != null) {
+				return filterCallback(result);
+			}
+		}
+	});
+});
+
+Template.messagePopup.onRendered(function() {
+	if (this.data.getInput != null) {
+		this.input = typeof this.data.getInput === 'function' && this.data.getInput();
+	} else if (this.data.input) {
+		this.input = this.parentTemplate().find(this.data.input);
+	}
+	if (this.input == null) {
+		console.error('Input not found for popup');
+	}
+	$(this.input).on('keyup', this.onInputKeyup.bind(this));
+	$(this.input).on('keydown', this.onInputKeydown.bind(this));
+	$(this.input).on('focus', this.onFocus.bind(this));
+	return $(this.input).on('blur', this.onBlur.bind(this));
+});
+
+Template.messagePopup.onDestroyed(function() {
+	$(this.input).off('keyup', this.onInputKeyup);
+	$(this.input).off('keydown', this.onInputKeydown);
+	$(this.input).off('focus', this.onFocus);
+	return $(this.input).off('blur', this.onBlur);
+});
+
+Template.messagePopup.events({
+	'mouseenter .popup-item'(e) {
+		if (e.currentTarget.className.indexOf('selected') > -1) {
+			return;
+		}
+		const template = Template.instance();
+		const current = template.find('.popup-item.selected');
+		if (current != null) {
+			current.className = current.className.replace(/\sselected/, '');
+		}
+		e.currentTarget.className += ' selected';
+		return template.value.set(this._id);
+	},
+	'mousedown .popup-item, touchstart .popup-item'() {
+		const template = Template.instance();
+		return template.clickingItem = true;
+	},
+	'mouseup .popup-item, touchend .popup-item'() {
+		const template = Template.instance();
+		template.clickingItem = false;
+		template.value.set(this._id);
+		template.enterValue();
+		template.open.set(false);
+		return toolbarSearch.clear();
+	}
+});
+
+Template.messagePopup.helpers({
+	isOpen() {
+		return Template.instance().open.get() && ((Template.instance().hasData.get() || (Template.instance().data.emptyTemplate != null)) || !Template.instance().parentTemplate(1).subscriptionsReady());
+	},
+	data() {
+		const template = Template.instance();
+		return template.records.get();
+	}
+});
diff --git a/packages/rocketchat-ui-message/client/popup/messagePopupConfig.coffee b/packages/rocketchat-ui-message/client/popup/messagePopupConfig.coffee
deleted file mode 100644
index a5bc17cc33fc8ed2fd76ebf284b1ac7c42028ec2..0000000000000000000000000000000000000000
--- a/packages/rocketchat-ui-message/client/popup/messagePopupConfig.coffee
+++ /dev/null
@@ -1,235 +0,0 @@
-@filteredUsersMemory = new Mongo.Collection null
-
-Meteor.startup ->
-	Tracker.autorun ->
-		if not Meteor.user()? or not Session.get('openedRoom')?
-			return
-
-		filteredUsersMemory.remove({})
-		messageUsers = RocketChat.models.Messages.find({rid: Session.get('openedRoom'), 'u.username': {$ne: Meteor.user().username}}, {fields: {'u.username': 1, 'u.name': 1, ts: 1}, sort: {ts: -1}}).fetch()
-		uniqueMessageUsersControl = {}
-		messageUsers.forEach (messageUser) ->
-			if not uniqueMessageUsersControl[messageUser.u.username]?
-				uniqueMessageUsersControl[messageUser.u.username] = true
-				filteredUsersMemory.upsert messageUser.u.username,
-					_id: messageUser.u.username
-					username: messageUser.u.username
-					name: messageUser.u.name
-					status: Session.get('user_' + messageUser.u.username + '_status') or 'offline'
-					ts: messageUser.ts
-
-
-getUsersFromServer = (filter, records, cb) =>
-	messageUsers = _.pluck(records, 'username')
-	Meteor.call 'spotlight', filter, messageUsers, { users: true }, (err, results) ->
-		if err?
-			return console.error err
-
-		if results.users.length > 0
-			for result in results.users
-				if records.length < 5
-					records.push
-						_id: result.username
-						username: result.username
-						status: 'offline'
-						sort: 3
-
-			records = _.sortBy(records, 'sort')
-
-			cb(records)
-
-getRoomsFromServer = (filter, records, cb) =>
-	Meteor.call 'spotlight', filter, null, { rooms: true }, (err, results) ->
-		if err?
-			return console.error err
-
-		if results.rooms.length > 0
-			for room in results.rooms
-				if records.length < 5
-					records.push room
-
-			cb(records)
-
-getUsersFromServerDelayed = _.throttle getUsersFromServer, 500
-getRoomsFromServerDelayed = _.throttle getRoomsFromServer, 500
-
-
-Template.messagePopupConfig.helpers
-	popupUserConfig: ->
-		self = this
-		template = Template.instance()
-
-		config =
-			title: t('People')
-			collection: filteredUsersMemory
-			template: 'messagePopupUser'
-			getInput: self.getInput
-			textFilterDelay: 200
-			trigger: '@'
-			suffix: ' '
-			getFilter: (collection, filter, cb) ->
-				exp = new RegExp("#{RegExp.escape filter}", 'i')
-
-				# Get users from messages
-				items = filteredUsersMemory.find({ts: {$exists: true}, $or: [{username: exp}, {name: exp}]}, {limit: 5, sort: {ts: -1}}).fetch()
-
-				# Get online users
-				if items.length < 5 and filter?.trim() isnt ''
-					messageUsers = _.pluck(items, 'username')
-					Meteor.users.find({$and: [{$or:[{username: exp}, {name: exp}]}, {username: {$nin: [Meteor.user()?.username].concat(messageUsers)}}]}, {limit: 5 - messageUsers.length}).fetch().forEach (item) ->
-						items.push
-							_id: item.username
-							username: item.username
-							name: item.name
-							status: item.status
-							sort: 1
-
-				# # Get users of room
-				# if items.length < 5 and filter?.trim() isnt ''
-				# 	messageUsers = _.pluck(items, 'username')
-				# 	Tracker.nonreactive ->
-				# 		roomUsernames = RocketChat.models.Rooms.findOne(Session.get('openedRoom')).usernames
-				# 		for roomUsername in roomUsernames
-				# 			if messageUsers.indexOf(roomUsername) is -1 and exp.test(roomUsername)
-				# 				items.push
-				# 					_id: roomUsername
-				# 					username: roomUsername
-				# 					status: Session.get('user_' + roomUsername + '_status') or 'offline'
-				# 					sort: 2
-
-				# 				if items.length >= 5
-				# 					break
-
-				# Get users from db
-				if items.length < 5 and filter?.trim() isnt ''
-					getUsersFromServerDelayed filter, items, cb
-
-				all =
-					_id: 'all'
-					username: 'all'
-					system: true
-					name: t 'Notify_all_in_this_room'
-					compatibility: 'channel group'
-					sort: 4
-
-				exp = new RegExp("(^|\\s)#{RegExp.escape filter}", 'i')
-				if exp.test(all.username) or exp.test(all.compatibility)
-					items.push all
-
-				here =
-					_id: 'here'
-					username: 'here'
-					system: true
-					name: t 'Notify_active_in_this_room'
-					compatibility: 'channel group'
-					sort: 4
-
-				if exp.test(here.username) or exp.test(here.compatibility)
-					items.push here
-
-				return items
-
-			getValue: (_id) ->
-				return _id
-
-		return config
-
-	popupChannelConfig: ->
-		self = this
-		template = Template.instance()
-
-		config =
-			title: t('Channels')
-			collection: RocketChat.models.Subscriptions
-			trigger: '#'
-			suffix: ' '
-			template: 'messagePopupChannel'
-			getInput: self.getInput
-			getFilter: (collection, filter, cb) ->
-				exp = new RegExp(filter, 'i')
-
-				records = collection.find({name: exp, t: {$in: ['c', 'p']}}, {limit: 5, sort: {ls: -1}}).fetch()
-
-				if records.length < 5 and filter?.trim() isnt ''
-					getRoomsFromServerDelayed filter, records, cb
-
-				return records
-
-			getValue: (_id, collection, records) ->
-				return _.findWhere(records, {_id: _id})?.name
-
-		return config
-
-	popupSlashCommandsConfig: ->
-		self = this
-		template = Template.instance()
-
-		config =
-			title: t('Commands')
-			collection: RocketChat.slashCommands.commands
-			trigger: '/'
-			suffix: ' '
-			triggerAnywhere: false
-			template: 'messagePopupSlashCommand'
-			getInput: self.getInput
-			getFilter: (collection, filter) ->
-				commands = []
-				for command, item of collection
-					if command.indexOf(filter) > -1
-						commands.push
-							_id: command
-							params: if item.params then TAPi18n.__ item.params else ''
-							description: TAPi18n.__ item.description
-
-				commands = commands.sort (a, b) ->
-					return a._id > b._id
-
-				commands = commands[0..10]
-
-				return commands
-
-		return config
-
-	emojiEnabled: ->
-		return RocketChat.emoji?
-
-	popupEmojiConfig: ->
-		if RocketChat.emoji?
-			self = this
-			template = Template.instance()
-			config =
-				title: t('Emoji')
-				collection: RocketChat.emoji.list
-				template: 'messagePopupEmoji'
-				trigger: ':'
-				prefix: ''
-				suffix: ' '
-				getInput: self.getInput
-				getFilter: (collection, filter, cb) ->
-					results = []
-					key = ':' + filter
-
-					if RocketChat.emoji.packages.emojione?.asciiList[key] or filter.length < 2
-						return []
-
-					regExp = new RegExp('^' + RegExp.escape(key), 'i')
-
-					for key, value of collection
-						if results.length > 10
-							break
-
-						if regExp.test(key)
-							results.push
-								_id: key
-								data: value
-
-					results.sort (a, b) ->
-						if a._id < b._id
-							return -1
-						if a._id > b._id
-							return 1
-						return 0
-
-					return results
-
-		return config
diff --git a/packages/rocketchat-ui-message/client/popup/messagePopupConfig.js b/packages/rocketchat-ui-message/client/popup/messagePopupConfig.js
new file mode 100644
index 0000000000000000000000000000000000000000..797e77fa9ca4dbdd6c993f1228f88d0e96b2f9b4
--- /dev/null
+++ b/packages/rocketchat-ui-message/client/popup/messagePopupConfig.js
@@ -0,0 +1,295 @@
+/* globals filteredUsersMemory */
+filteredUsersMemory = new Mongo.Collection(null);
+
+Meteor.startup(function() {
+	Tracker.autorun(function() {
+		if (Meteor.user() == null || Session.get('openedRoom') == null) {
+			return;
+		}
+		filteredUsersMemory.remove({});
+		const messageUsers = RocketChat.models.Messages.find({
+			rid: Session.get('openedRoom'),
+			'u.username': {
+				$ne: Meteor.user().username
+			}
+		}, {
+			fields: {
+				'u.username': 1,
+				'u.name': 1,
+				ts: 1
+			},
+			sort: {
+				ts: -1
+			}
+		}).fetch();
+		const uniqueMessageUsersControl = {};
+		return messageUsers.forEach(function(messageUser) {
+			if (uniqueMessageUsersControl[messageUser.u.username] == null) {
+				uniqueMessageUsersControl[messageUser.u.username] = true;
+				return filteredUsersMemory.upsert(messageUser.u.username, {
+					_id: messageUser.u.username,
+					username: messageUser.u.username,
+					name: messageUser.u.name,
+					status: Session.get(`user_${ messageUser.u.username }_status`) || 'offline',
+					ts: messageUser.ts
+				});
+			}
+		});
+	});
+});
+
+const getUsersFromServer = (filter, records, cb) => {
+	const messageUsers = _.pluck(records, 'username');
+	return Meteor.call('spotlight', filter, messageUsers, {
+		users: true
+	}, function(err, results) {
+		if (err != null) {
+			return console.error(err);
+		}
+		if (results.users.length > 0) {
+			results.users.forEach(result => {
+				if (records.length < 5) {
+					records.push({
+						_id: result.username,
+						username: result.username,
+						status: 'offline',
+						sort: 3
+					});
+				}
+			});
+			records = _.sortBy(records, 'sort');
+			return cb(records);
+		}
+	});
+};
+
+const getRoomsFromServer = (filter, records, cb) => {
+	return Meteor.call('spotlight', filter, null, {
+		rooms: true
+	}, function(err, results) {
+		if (err != null) {
+			return console.error(err);
+		}
+		if (results.rooms.length > 0) {
+			results.rooms.forEach(room => {
+				if (records.length < 5) {
+					records.push(room);
+				}
+			});
+			return cb(records);
+		}
+	});
+};
+
+const getUsersFromServerDelayed = _.throttle(getUsersFromServer, 500);
+
+const getRoomsFromServerDelayed = _.throttle(getRoomsFromServer, 500);
+
+Template.messagePopupConfig.helpers({
+	popupUserConfig() {
+		const self = this;
+		const config = {
+			title: t('People'),
+			collection: filteredUsersMemory,
+			template: 'messagePopupUser',
+			getInput: self.getInput,
+			textFilterDelay: 200,
+			trigger: '@',
+			suffix: ' ',
+			getFilter(collection, filter, cb) {
+				let exp = new RegExp(`${ RegExp.escape(filter) }`, 'i');
+				// Get users from messages
+				const items = filteredUsersMemory.find({
+					ts: {
+						$exists: true
+					},
+					$or: [
+						{
+							username: exp
+						}, {
+							name: exp
+						}
+					]
+				}, {
+					limit: 5,
+					sort: {
+						ts: -1
+					}
+				}).fetch();
+				// Get online users
+				if (items.length < 5 && filter && filter.trim() !== '') {
+					const messageUsers = _.pluck(items, 'username');
+					const user = Meteor.user();
+					items.push(...Meteor.users.find({
+						$and: [
+							{
+								$or: [
+									{
+										username: exp
+									}, {
+										name: exp
+									}
+								]
+							}, {
+								username: {
+									$nin: [(user && user.username), ...messageUsers]
+								}
+							}
+						]
+					}, {
+						limit: 5 - messageUsers.length
+					}).fetch().map(function(item) {
+						return {
+							_id: item.username,
+							username: item.username,
+							name: item.name,
+							status: item.status,
+							sort: 1
+						};
+					}));
+				}
+				// Get users from db
+				if (items.length < 5 && filter && filter.trim() !== '') {
+					getUsersFromServerDelayed(filter, items, cb);
+				}
+				const all = {
+					_id: 'all',
+					username: 'all',
+					system: true,
+					name: t('Notify_all_in_this_room'),
+					compatibility: 'channel group',
+					sort: 4
+				};
+				exp = new RegExp(`(^|\\s)${ RegExp.escape(filter) }`, 'i');
+				if (exp.test(all.username) || exp.test(all.compatibility)) {
+					items.push(all);
+				}
+				const here = {
+					_id: 'here',
+					username: 'here',
+					system: true,
+					name: t('Notify_active_in_this_room'),
+					compatibility: 'channel group',
+					sort: 4
+				};
+				if (exp.test(here.username) || exp.test(here.compatibility)) {
+					items.push(here);
+				}
+				return items;
+			},
+			getValue(_id) {
+				return _id;
+			}
+		};
+		return config;
+	},
+	popupChannelConfig() {
+		const self = this;
+		const config = {
+			title: t('Channels'),
+			collection: RocketChat.models.Subscriptions,
+			trigger: '#',
+			suffix: ' ',
+			template: 'messagePopupChannel',
+			getInput: self.getInput,
+			getFilter(collection, filter, cb) {
+				const exp = new RegExp(filter, 'i');
+				const records = collection.find({
+					name: exp,
+					t: {
+						$in: ['c', 'p']
+					}
+				}, {
+					limit: 5,
+					sort: {
+						ls: -1
+					}
+				}).fetch();
+
+				if (records.length < 5 && filter && filter.trim() !== '') {
+					getRoomsFromServerDelayed(filter, records, cb);
+				}
+				return records;
+			},
+			getValue(_id, collection, records) {
+				const record = _.findWhere(records, {
+					_id
+				});
+				return record && record.name;
+			}
+		};
+		return config;
+	},
+	popupSlashCommandsConfig() {
+		const self = this;
+		const config = {
+			title: t('Commands'),
+			collection: RocketChat.slashCommands.commands,
+			trigger: '/',
+			suffix: ' ',
+			triggerAnywhere: false,
+			template: 'messagePopupSlashCommand',
+			getInput: self.getInput,
+			getFilter(collection, filter) {
+				return Object.keys(collection).map(command => {
+					const item = collection[command];
+					return {
+						_id: command,
+						params: item.params ? TAPi18n.__(item.params) : '',
+						description: TAPi18n.__(item.description)
+					};
+				})
+				.filter(command => command._id.indexOf(filter) > -1)
+				.sort(function(a, b) {
+					return a._id > b._id;
+				})
+				.slice(0, 11);
+			}
+		};
+		return config;
+	},
+	emojiEnabled() {
+		return RocketChat.emoji != null;
+	},
+	popupEmojiConfig() {
+		if (RocketChat.emoji != null) {
+			const self = this;
+			return {
+				title: t('Emoji'),
+				collection: RocketChat.emoji.list,
+				template: 'messagePopupEmoji',
+				trigger: ':',
+				prefix: '',
+				suffix: ' ',
+				getInput: self.getInput,
+				getFilter(collection, filter) {
+					const key = `:${ filter }`;
+
+					if (!RocketChat.emoji.packages.emojione || RocketChat.emoji.packages.emojione.asciiList[key] || filter.length < 2) {
+						return [];
+					}
+
+					const regExp = new RegExp(`^${ RegExp.escape(key) }`, 'i');
+					return Object.keys(collection).map(key => {
+						const value = collection[key];
+						return {
+							_id: key,
+							data: value
+						};
+					})
+					.filter(obj => regExp.test(obj._id))
+					.slice(0, 10)
+					.sort(function(a, b) {
+						if (a._id < b._id) {
+							return -1;
+						}
+						if (a._id > b._id) {
+							return 1;
+						}
+						return 0;
+					});
+				}
+			};
+		}
+	}
+});
diff --git a/packages/rocketchat-ui-message/client/popup/messagePopupEmoji.coffee b/packages/rocketchat-ui-message/client/popup/messagePopupEmoji.coffee
deleted file mode 100644
index d50a31f2c236aa0a71d4bc5144f84d2101e4a68d..0000000000000000000000000000000000000000
--- a/packages/rocketchat-ui-message/client/popup/messagePopupEmoji.coffee
+++ /dev/null
@@ -1,4 +0,0 @@
-Template.messagePopupEmoji.helpers
-	value: ->
-		length = this.data.length
-		return this.data[length - 1]
diff --git a/packages/rocketchat-ui-message/client/popup/messagePopupEmoji.js b/packages/rocketchat-ui-message/client/popup/messagePopupEmoji.js
new file mode 100644
index 0000000000000000000000000000000000000000..934067a235e72a70cbf29af8cd85cb262cd640c8
--- /dev/null
+++ b/packages/rocketchat-ui-message/client/popup/messagePopupEmoji.js
@@ -0,0 +1,6 @@
+Template.messagePopupEmoji.helpers({
+	value() {
+		const length = this.data.length;
+		return this.data[length - 1];
+	}
+});
diff --git a/packages/rocketchat-ui-message/package.js b/packages/rocketchat-ui-message/package.js
index 4d26d912832fbb5d6ff45c6ab902d02cabf39663..bcf2b9e3ddfd66b221c62bd5091f7ecab70fdcd1 100644
--- a/packages/rocketchat-ui-message/package.js
+++ b/packages/rocketchat-ui-message/package.js
@@ -15,7 +15,6 @@ Package.onUse(function(api) {
 		'mongo',
 		'ecmascript',
 		'templating',
-		'coffeescript',
 		'underscore',
 		'tracker',
 		'rocketchat:lib',
@@ -33,12 +32,12 @@ Package.onUse(function(api) {
 	api.addFiles('client/popup/messagePopupSlashCommand.html', 'client');
 	api.addFiles('client/popup/messagePopupUser.html', 'client');
 
-	api.addFiles('client/message.coffee', 'client');
-	api.addFiles('client/messageBox.coffee', 'client');
-	api.addFiles('client/popup/messagePopup.coffee', 'client');
+	api.addFiles('client/message.js', 'client');
+	api.addFiles('client/messageBox.js', 'client');
+	api.addFiles('client/popup/messagePopup.js', 'client');
 	api.addFiles('client/popup/messagePopupChannel.js', 'client');
-	api.addFiles('client/popup/messagePopupConfig.coffee', 'client');
-	api.addFiles('client/popup/messagePopupEmoji.coffee', 'client');
+	api.addFiles('client/popup/messagePopupConfig.js', 'client');
+	api.addFiles('client/popup/messagePopupEmoji.js', 'client');
 
 	api.addFiles('client/renderMessageBody.js', 'client');
 
diff --git a/packages/rocketchat-ui/client/lib/fileUpload.coffee b/packages/rocketchat-ui/client/lib/fileUpload.coffee
index 55507fe06189668c34d6907074167a26b5f58fe6..e4494b7d78683b94332b90b20b9f7b2505f282f0 100644
--- a/packages/rocketchat-ui/client/lib/fileUpload.coffee
+++ b/packages/rocketchat-ui/client/lib/fileUpload.coffee
@@ -9,6 +9,8 @@ getUploadPreview = (file, callback) ->
 	# If greater then 10MB don't try and show a preview
 	if file.file.size > 10 * 1000000
 		callback(file, null)
+	else if not file.file.type?
+		callback(file, null)
 	else
 		if file.file.type.indexOf('audio') > -1 or file.file.type.indexOf('video') > -1 or file.file.type.indexOf('image') > -1
 			file.type = file.file.type.split('/')[0]
diff --git a/packages/rocketchat-ui/client/lib/fireEvent.js b/packages/rocketchat-ui/client/lib/fireEvent.js
index 3a1ae3fe6c102a280980407250817f967bd7c2fa..a3456d3e61a6d235a9b1d180b6c1be4c54ea2718 100644
--- a/packages/rocketchat-ui/client/lib/fireEvent.js
+++ b/packages/rocketchat-ui/client/lib/fireEvent.js
@@ -1,4 +1,6 @@
 window.fireGlobalEvent = function _fireGlobalEvent(eventName, params) {
+	window.dispatchEvent(new CustomEvent(eventName, {detail: params}));
+
 	Tracker.autorun((computation) => {
 		const enabled = RocketChat.settings.get('Iframe_Integration_send_enable');
 		if (enabled === undefined) {
@@ -6,7 +8,6 @@ window.fireGlobalEvent = function _fireGlobalEvent(eventName, params) {
 		}
 		computation.stop();
 		if (enabled) {
-			window.dispatchEvent(new CustomEvent(eventName, {detail: params}));
 			parent.postMessage({
 				eventName,
 				data: params
@@ -14,3 +15,4 @@ window.fireGlobalEvent = function _fireGlobalEvent(eventName, params) {
 		}
 	});
 };
+
diff --git a/packages/rocketchat-webrtc/WebRTCClass.coffee b/packages/rocketchat-webrtc/WebRTCClass.coffee
deleted file mode 100644
index a957e411d7cc480832d41bd9cdc3ce66a4715c50..0000000000000000000000000000000000000000
--- a/packages/rocketchat-webrtc/WebRTCClass.coffee
+++ /dev/null
@@ -1,823 +0,0 @@
-emptyFn = ->
-	# empty
-
-class WebRTCTransportClass
-	debug: false
-
-	log: ->
-		if @debug is true
-			console.log.apply(console, arguments)
-
-	constructor: (@webrtcInstance) ->
-		@callbacks = {}
-
-		RocketChat.Notifications.onRoom @webrtcInstance.room, 'webrtc', (type, data) =>
-			@log 'WebRTCTransportClass - onRoom', type, data
-
-			switch type
-				when 'status'
-					if @callbacks['onRemoteStatus']?.length > 0
-						fn(data) for fn in @callbacks['onRemoteStatus']
-
-	onUserStream: (type, data) ->
-		if data.room isnt @webrtcInstance.room then return
-		@log 'WebRTCTransportClass - onUser', type, data
-
-		switch type
-			when 'call'
-				if @callbacks['onRemoteCall']?.length > 0
-					fn(data) for fn in @callbacks['onRemoteCall']
-
-			when 'join'
-				if @callbacks['onRemoteJoin']?.length > 0
-					fn(data) for fn in @callbacks['onRemoteJoin']
-
-			when 'candidate'
-				if @callbacks['onRemoteCandidate']?.length > 0
-					fn(data) for fn in @callbacks['onRemoteCandidate']
-
-			when 'description'
-				if @callbacks['onRemoteDescription']?.length > 0
-					fn(data) for fn in @callbacks['onRemoteDescription']
-
-	startCall: (data) ->
-		@log 'WebRTCTransportClass - startCall', @webrtcInstance.room, @webrtcInstance.selfId
-		RocketChat.Notifications.notifyUsersOfRoom @webrtcInstance.room, 'webrtc', 'call',
-			from: @webrtcInstance.selfId
-			room: @webrtcInstance.room
-			media: data.media
-			monitor: data.monitor
-
-	joinCall: (data) ->
-		@log 'WebRTCTransportClass - joinCall', @webrtcInstance.room, @webrtcInstance.selfId
-		if data.monitor is true
-			RocketChat.Notifications.notifyUser data.to, 'webrtc', 'join',
-				from: @webrtcInstance.selfId
-				room: @webrtcInstance.room
-				media: data.media
-				monitor: data.monitor
-		else
-			RocketChat.Notifications.notifyUsersOfRoom @webrtcInstance.room, 'webrtc', 'join',
-				from: @webrtcInstance.selfId
-				room: @webrtcInstance.room
-				media: data.media
-				monitor: data.monitor
-
-	sendCandidate: (data) ->
-		data.from = @webrtcInstance.selfId
-		data.room = @webrtcInstance.room
-		@log 'WebRTCTransportClass - sendCandidate', data
-		RocketChat.Notifications.notifyUser data.to, 'webrtc', 'candidate', data
-
-	sendDescription: (data) ->
-		data.from = @webrtcInstance.selfId
-		data.room = @webrtcInstance.room
-		@log 'WebRTCTransportClass - sendDescription', data
-		RocketChat.Notifications.notifyUser data.to, 'webrtc', 'description', data
-
-	sendStatus: (data) ->
-		@log 'WebRTCTransportClass - sendStatus', data, @webrtcInstance.room
-		data.from = @webrtcInstance.selfId
-		RocketChat.Notifications.notifyRoom @webrtcInstance.room, 'webrtc', 'status', data
-
-	onRemoteCall: (fn) ->
-		@callbacks['onRemoteCall'] ?= []
-		@callbacks['onRemoteCall'].push fn
-
-	onRemoteJoin: (fn) ->
-		@callbacks['onRemoteJoin'] ?= []
-		@callbacks['onRemoteJoin'].push fn
-
-	onRemoteCandidate: (fn) ->
-		@callbacks['onRemoteCandidate'] ?= []
-		@callbacks['onRemoteCandidate'].push fn
-
-	onRemoteDescription: (fn) ->
-		@callbacks['onRemoteDescription'] ?= []
-		@callbacks['onRemoteDescription'].push fn
-
-	onRemoteStatus: (fn) ->
-		@callbacks['onRemoteStatus'] ?= []
-		@callbacks['onRemoteStatus'].push fn
-
-
-class WebRTCClass
-	config:
-		iceServers: []
-
-	debug: false
-
-	transportClass: WebRTCTransportClass
-
-
-	###
-		@param seldId {String}
-		@param room {String}
-	###
-	constructor: (@selfId, @room) ->
-		@config.iceServers = []
-
-		servers = RocketChat.settings.get("WebRTC_Servers")
-		if servers?.trim() isnt ''
-			servers = servers.replace /\s/g, ''
-			servers = servers.split ','
-			for server in servers
-				server = server.split '@'
-				serverConfig =
-					urls: server.pop()
-
-				if server.length is 1
-					server = server[0].split ':'
-					serverConfig.username = decodeURIComponent(server[0])
-					serverConfig.credential = decodeURIComponent(server[1])
-
-				@config.iceServers.push serverConfig
-
-		@peerConnections = {}
-
-		@remoteItems = new ReactiveVar []
-		@remoteItemsById = new ReactiveVar {}
-		@callInProgress = new ReactiveVar false
-		@audioEnabled = new ReactiveVar true
-		@videoEnabled = new ReactiveVar true
-		@overlayEnabled = new ReactiveVar false
-		@screenShareEnabled = new ReactiveVar false
-		@localUrl = new ReactiveVar
-
-		@active = false
-		@remoteMonitoring = false
-		@monitor = false
-		@autoAccept = false
-
-		@navigator = undefined
-		userAgent = navigator.userAgent.toLocaleLowerCase();
-		if userAgent.indexOf('electron') isnt -1
-			@navigator = 'electron'
-		else if userAgent.indexOf('chrome') isnt -1
-			@navigator = 'chrome'
-		else if userAgent.indexOf('firefox') isnt -1
-			@navigator = 'firefox'
-		else if userAgent.indexOf('safari') isnt -1
-			@navigator = 'safari'
-
-		@screenShareAvailable = @navigator in ['chrome', 'firefox', 'electron']
-
-		@media =
-			video: false
-			audio: true
-
-		@transport = new @transportClass @
-
-		@transport.onRemoteCall @onRemoteCall.bind @
-		@transport.onRemoteJoin @onRemoteJoin.bind @
-		@transport.onRemoteCandidate @onRemoteCandidate.bind @
-		@transport.onRemoteDescription @onRemoteDescription.bind @
-		@transport.onRemoteStatus @onRemoteStatus.bind @
-
-		Meteor.setInterval @checkPeerConnections.bind(@), 1000
-
-		# Meteor.setInterval @broadcastStatus.bind(@), 1000
-
-	log: ->
-		if @debug is true
-			console.log.apply(console, arguments)
-
-	onError: ->
-		console.error.apply(console, arguments)
-
-	checkPeerConnections: ->
-		for id, peerConnection of @peerConnections
-			if peerConnection.iceConnectionState not in ['connected', 'completed'] and peerConnection.createdAt + 5000 < Date.now()
-				@stopPeerConnection id
-
-	updateRemoteItems: ->
-		items = []
-		itemsById = {}
-
-		for id, peerConnection of @peerConnections
-			for remoteStream in peerConnection.getRemoteStreams()
-				item =
-					id: id
-					url: URL.createObjectURL(remoteStream)
-					state: peerConnection.iceConnectionState
-
-				switch peerConnection.iceConnectionState
-					when 'checking'
-						item.stateText = 'Connecting...'
-
-					when 'connected', 'completed'
-						item.stateText = 'Connected'
-						item.connected = true
-
-					when 'disconnected'
-						item.stateText = 'Disconnected'
-
-					when 'failed'
-						item.stateText = 'Failed'
-
-					when 'closed'
-						item.stateText = 'Closed'
-
-				items.push item
-				itemsById[id] = item
-
-		@remoteItems.set items
-		@remoteItemsById.set itemsById
-
-	resetCallInProgress: ->
-		@callInProgress.set false
-
-	broadcastStatus: ->
-		if @active isnt true or @monitor is true or @remoteMonitoring is true then return
-
-		remoteConnections = []
-		for id, peerConnection of @peerConnections
-			remoteConnections.push
-				id: id
-				media: peerConnection.remoteMedia
-
-		@transport.sendStatus
-			media: @media
-			remoteConnections: remoteConnections
-
-	###
-		@param data {Object}
-			from {String}
-			media {Object}
-			remoteConnections {Array[Object]}
-				id {String}
-				media {Object}
-	###
-	onRemoteStatus: (data) ->
-		# @log 'onRemoteStatus', arguments
-
-		@callInProgress.set true
-
-		Meteor.clearTimeout @callInProgressTimeout
-		@callInProgressTimeout = Meteor.setTimeout @resetCallInProgress.bind(@), 2000
-
-		if @active isnt true then return
-
-		remoteConnections = [{id: data.from, media: data.media}].concat data.remoteConnections
-
-		for remoteConnection in remoteConnections
-			if remoteConnection.id isnt @selfId and not @peerConnections[remoteConnection.id]?
-				@log 'reconnecting with', remoteConnection.id
-				@onRemoteJoin
-					from: remoteConnection.id
-					media: remoteConnection.media
-
-	###
-		@param id {String}
-	###
-	getPeerConnection: (id) ->
-		return @peerConnections[id] if @peerConnections[id]?
-
-		peerConnection = new RTCPeerConnection @config
-
-		peerConnection.createdAt = Date.now()
-		peerConnection.remoteMedia = {}
-
-		@peerConnections[id] = peerConnection
-
-		eventNames = [
-			'icecandidate'
-			'addstream'
-			'removestream'
-			'iceconnectionstatechange'
-			'datachannel'
-			'identityresult'
-			'idpassertionerror'
-			'idpvalidationerror'
-			'negotiationneeded'
-			'peeridentity'
-			'signalingstatechange'
-		]
-
-		for eventName in eventNames
-			peerConnection.addEventListener eventName, (e) =>
-				@log id, e.type, e
-
-		peerConnection.addEventListener 'icecandidate', (e) =>
-			if not e.candidate?
-				return
-
-			@transport.sendCandidate
-				to: id
-				candidate:
-					candidate: e.candidate.candidate
-					sdpMLineIndex: e.candidate.sdpMLineIndex
-					sdpMid: e.candidate.sdpMid
-
-		peerConnection.addEventListener 'addstream', (e) =>
-			@updateRemoteItems()
-
-		peerConnection.addEventListener 'removestream', (e) =>
-			@updateRemoteItems()
-
-		peerConnection.addEventListener 'iceconnectionstatechange', (e) =>
-			if peerConnection.iceConnectionState in ['disconnected', 'closed'] and peerConnection is @peerConnections[id]
-				@stopPeerConnection id
-				Meteor.setTimeout =>
-					if Object.keys(@peerConnections).length is 0
-						@stop()
-				, 3000
-
-			@updateRemoteItems()
-
-		return peerConnection
-
-	_getUserMedia: (media, onSuccess, onError) ->
-		onSuccessLocal = (stream) ->
-			if AudioContext? and stream.getAudioTracks().length > 0
-				audioContext = new AudioContext
-				source = audioContext.createMediaStreamSource(stream)
-
-				volume = audioContext.createGain()
-				source.connect(volume)
-				peer = audioContext.createMediaStreamDestination()
-				volume.connect(peer)
-				volume.gain.value = 0.6
-
-				stream.removeTrack(stream.getAudioTracks()[0])
-				stream.addTrack(peer.stream.getAudioTracks()[0])
-				stream.volume = volume
-
-				this.audioContext = audioContext
-
-			onSuccess(stream)
-
-		navigator.getUserMedia media, onSuccessLocal, onError
-
-
-	getUserMedia: (media, onSuccess, onError=@onError) ->
-		if media.desktop isnt true
-			@_getUserMedia media, onSuccess, onError
-			return
-
-		if @screenShareAvailable isnt true
-			console.log 'Screen share is not avaliable'
-			return
-
-		getScreen = (audioStream) =>
-			if document.cookie.indexOf("rocketchatscreenshare=chrome") is -1 and not window.rocketchatscreenshare? and @navigator isnt 'electron'
-				refresh = ->
-					swal
-						type: "warning"
-						title: TAPi18n.__ "Refresh_your_page_after_install_to_enable_screen_sharing"
-
-				swal
-					type: "warning"
-					title: TAPi18n.__ "Screen_Share"
-					text: TAPi18n.__ "You_need_install_an_extension_to_allow_screen_sharing"
-					html: true
-					showCancelButton: true
-					confirmButtonText: TAPi18n.__ "Install_Extension"
-					cancelButtonText: TAPi18n.__ "Cancel"
-				, (isConfirm) =>
-					if isConfirm
-						if @navigator is 'chrome'
-							url = 'https://chrome.google.com/webstore/detail/rocketchat-screen-share/nocfbnnmjnndkbipkabodnheejiegccf'
-							try
-								chrome.webstore.install url, refresh, ->
-									window.open(url)
-									refresh()
-							catch e
-								window.open(url)
-								refresh()
-						else if @navigator is 'firefox'
-							window.open('https://addons.mozilla.org/en-GB/firefox/addon/rocketchat-screen-share/')
-							refresh()
-
-				return onError(false)
-
-			getScreenSuccess = (stream) =>
-				if audioStream?
-					stream.addTrack(audioStream.getAudioTracks()[0])
-				onSuccess(stream)
-
-			if @navigator is 'firefox'
-				media =
-					audio: media.audio
-					video:
-						mozMediaSource: 'window'
-						mediaSource: 'window'
-				@_getUserMedia media, getScreenSuccess, onError
-			else
-				ChromeScreenShare.getSourceId @navigator, (id) =>
-					media =
-						audio: false
-						video:
-							mandatory:
-								chromeMediaSource: 'desktop'
-								chromeMediaSourceId: id
-								maxWidth: 1280
-								maxHeight: 720
-
-					@_getUserMedia media, getScreenSuccess, onError
-
-		if @navigator is 'firefox' or not media.audio? or media.audio is false
-			getScreen()
-		else
-			getAudioSuccess = (audioStream) =>
-				getScreen(audioStream)
-
-			getAudioError = =>
-				getScreen()
-
-			@_getUserMedia {audio: media.audio}, getAudioSuccess, getAudioError
-
-
-	###
-		@param callback {Function}
-	###
-	getLocalUserMedia: (callback) ->
-		@log 'getLocalUserMedia', arguments
-
-		if @localStream?
-			return callback null, @localStream
-
-		onSuccess = (stream) =>
-			@localStream = stream
-			@localUrl.set URL.createObjectURL(stream)
-
-			@videoEnabled.set @media.video is true
-			@audioEnabled.set @media.audio is true
-
-			for id, peerConnection of @peerConnections
-				peerConnection.addStream stream
-
-			callback null, @localStream
-
-		onError = (error) =>
-			callback false
-			@onError error
-
-		@getUserMedia @media, onSuccess, onError
-
-
-	###
-		@param id {String}
-	###
-	stopPeerConnection: (id) ->
-		peerConnection = @peerConnections[id]
-		if not peerConnection? then return
-
-		delete @peerConnections[id]
-		peerConnection.close()
-
-		@updateRemoteItems()
-
-	stopAllPeerConnections: ->
-		for id, peerConnection of @peerConnections
-			@stopPeerConnection id
-		window.audioContext?.close()
-
-	setAudioEnabled: (enabled=true) ->
-		if @localStream?
-			if enabled is true and @media.audio isnt true
-				delete @localStream
-				@media.audio = true
-				@getLocalUserMedia =>
-					@stopAllPeerConnections()
-					@joinCall()
-			else
-				@localStream.getAudioTracks().forEach (audio) -> audio.enabled = enabled
-				@audioEnabled.set enabled
-
-	disableAudio: ->
-		@setAudioEnabled false
-
-	enableAudio: ->
-		@setAudioEnabled true
-
-	setVideoEnabled: (enabled=true) ->
-		if @localStream?
-			if enabled is true and @media.video isnt true
-				delete @localStream
-				@media.video = true
-				@getLocalUserMedia =>
-					@stopAllPeerConnections()
-					@joinCall()
-			else
-				@localStream.getVideoTracks().forEach (video) -> video.enabled = enabled
-				@videoEnabled.set enabled
-
-	disableScreenShare: ->
-		@setScreenShareEnabled false
-
-	enableScreenShare: ->
-		@setScreenShareEnabled true
-
-	setScreenShareEnabled: (enabled=true) ->
-		if @localStream?
-			@media.desktop = enabled
-			delete @localStream
-			@getLocalUserMedia (err) =>
-				if err?
-					return
-				@screenShareEnabled.set enabled
-				@stopAllPeerConnections()
-				@joinCall()
-
-	disableVideo: ->
-		@setVideoEnabled false
-
-	enableVideo: ->
-		@setVideoEnabled true
-
-	stop: ->
-		@active = false
-		@monitor = false
-		@remoteMonitoring = false
-		if @localStream? and typeof @localStream isnt 'undefined'
-			@localStream.getTracks().forEach (track) ->
-				track.stop()
-		@localUrl.set undefined
-		delete @localStream
-
-		@stopAllPeerConnections()
-
-
-	###
-		@param media {Object}
-			audio {Boolean}
-			video {Boolean}
-	###
-	startCall: (media={}) ->
-		@log 'startCall', arguments
-		@media = media
-		@getLocalUserMedia =>
-			@active = true
-			@transport.startCall
-				media: @media
-
-	startCallAsMonitor: (media={}) ->
-		@log 'startCallAsMonitor', arguments
-		@media = media
-		@active = true
-		@monitor = true
-		@transport.startCall
-			media: @media
-			monitor: true
-
-
-	###
-		@param data {Object}
-			from {String}
-			monitor {Boolean}
-			media {Object}
-				audio {Boolean}
-				video {Boolean}
-	###
-	onRemoteCall: (data) ->
-		if @autoAccept is true
-			FlowRouter.goToRoomById data.room
-			Meteor.defer =>
-				@joinCall
-					to: data.from
-					monitor: data.monitor
-					media: data.media
-			return
-
-		fromUsername = Meteor.users.findOne(data.from)?.username
-		subscription = ChatSubscription.findOne({rid: data.room})
-
-		if data.monitor is true
-			icon = 'eye'
-			title = "Monitor call from #{fromUsername}"
-		else if subscription?.t is 'd'
-			if data.media?.video
-				icon = 'videocam'
-				title = "Direct video call from #{fromUsername}"
-			else
-				icon = 'phone'
-				title = "Direct audio call from #{fromUsername}"
-		else
-			if data.media?.video
-				icon = 'videocam'
-				title = "Group video call from #{subscription.name}"
-			else
-				icon = 'phone'
-				title = "Group audio call from #{subscription.name}"
-
-		swal
-			title: "<i class='icon-#{icon} alert-icon success-color'></i>#{title}"
-			text: "Do you want to accept?"
-			html: true
-			showCancelButton: true
-			confirmButtonText: "Yes"
-			cancelButtonText: "No"
-		, (isConfirm) =>
-			if isConfirm
-				FlowRouter.goToRoomById data.room
-				Meteor.defer =>
-					@joinCall
-						to: data.from
-						monitor: data.monitor
-						media: data.media
-			else
-				@stop()
-
-
-	###
-		@param data {Object}
-			to {String}
-			monitor {Boolean}
-			media {Object}
-				audio {Boolean}
-				video {Boolean}
-				desktop {Boolean}
-	###
-	joinCall: (data={}) ->
-		if data.media?.audio?
-			@media.audio = data.media.audio
-
-		if data.media?.video?
-			@media.video = data.media.video
-
-		data.media = @media
-
-		@log 'joinCall', arguments
-		@getLocalUserMedia =>
-			@remoteMonitoring = data.monitor
-			@active = true
-			@transport.joinCall(data)
-
-
-	###
-		@param data {Object}
-			from {String}
-			monitor {Boolean}
-			media {Object}
-				audio {Boolean}
-				video {Boolean}
-				desktop {Boolean}
-	###
-	onRemoteJoin: (data) ->
-		if @active isnt true then return
-
-		@log 'onRemoteJoin', arguments
-
-		peerConnection = @getPeerConnection data.from
-
-		# needsRefresh = false
-		# if peerConnection.iceConnectionState isnt 'new'
-		# 	needsAudio = data.media.audio is true and peerConnection.remoteMedia.audio isnt true
-		# 	needsVideo = data.media.video is true and peerConnection.remoteMedia.video isnt true
-		# 	needsRefresh = needsAudio or needsVideo or data.media.desktop isnt peerConnection.remoteMedia.desktop
-
-		# if peerConnection.signalingState is "have-local-offer" or needsRefresh
-		if peerConnection.signalingState isnt "checking"
-			@stopPeerConnection data.from
-			peerConnection = @getPeerConnection data.from
-
-		if peerConnection.iceConnectionState isnt 'new'
-			return
-
-		peerConnection.remoteMedia = data.media
-
-		peerConnection.addStream @localStream if @localStream
-
-		onOffer = (offer) =>
-			onLocalDescription = =>
-				@transport.sendDescription
-					to: data.from
-					type: 'offer'
-					ts: peerConnection.createdAt
-					media: @media
-					description:
-						sdp: offer.sdp
-						type: offer.type
-
-			peerConnection.setLocalDescription(new RTCSessionDescription(offer), onLocalDescription, @onError)
-
-		if data.monitor is true
-			peerConnection.createOffer onOffer, @onError,
-				mandatory:
-					OfferToReceiveAudio: data.media.audio
-					OfferToReceiveVideo: data.media.video
-		else
-			peerConnection.createOffer(onOffer, @onError)
-
-
-	###
-		@param data {Object}
-			from {String}
-			ts {Integer}
-			description {String}
-	###
-	onRemoteOffer: (data) ->
-		if @active isnt true then return
-
-		@log 'onRemoteOffer', arguments
-		peerConnection = @getPeerConnection data.from
-
-		if peerConnection.signalingState in ["have-local-offer", "stable"] and peerConnection.createdAt < data.ts
-			@stopPeerConnection data.from
-			peerConnection = @getPeerConnection data.from
-
-		if peerConnection.iceConnectionState isnt 'new'
-			return
-
-		peerConnection.setRemoteDescription new RTCSessionDescription(data.description)
-
-		try peerConnection.addStream @localStream if @localStream
-
-		onAnswer = (answer) =>
-			onLocalDescription = =>
-				@transport.sendDescription
-					to: data.from
-					type: 'answer'
-					ts: peerConnection.createdAt
-					description:
-						sdp: answer.sdp
-						type: answer.type
-
-			peerConnection.setLocalDescription(new RTCSessionDescription(answer), onLocalDescription, @onError)
-
-		peerConnection.createAnswer(onAnswer, @onError)
-
-
-	###
-		@param data {Object}
-			to {String}
-			from {String}
-			candidate {RTCIceCandidate JSON encoded}
-	###
-	onRemoteCandidate: (data) ->
-		if @active isnt true then return
-		if data.to isnt @selfId then return
-
-		@log 'onRemoteCandidate', arguments
-		peerConnection = @getPeerConnection data.from
-
-		if peerConnection.iceConnectionState not in ["closed", "failed", "disconnected", "completed"]
-			peerConnection.addIceCandidate new RTCIceCandidate(data.candidate)
-
-
-	###
-		@param data {Object}
-			to {String}
-			from {String}
-			type {String} [offer, answer]
-			description {RTCSessionDescription JSON encoded}
-			ts {Integer}
-			media {Object}
-				audio {Boolean}
-				video {Boolean}
-				desktop {Boolean}
-	###
-	onRemoteDescription: (data) ->
-		if @active isnt true then return
-		if data.to isnt @selfId then return
-
-		@log 'onRemoteDescription', arguments
-		peerConnection = @getPeerConnection data.from
-
-		if data.type is 'offer'
-			peerConnection.remoteMedia = data.media
-			@onRemoteOffer
-				from: data.from
-				ts: data.ts
-				description: data.description
-		else
-			peerConnection.setRemoteDescription new RTCSessionDescription(data.description)
-
-
-WebRTC = new class
-	constructor: ->
-		@instancesByRoomId = {}
-
-	getInstanceByRoomId: (roomId) ->
-		subscription = ChatSubscription.findOne({rid: roomId})
-		if not subscription
-			return
-
-		enabled = false
-		switch subscription.t
-			when 'd'
-				enabled = RocketChat.settings.get('WebRTC_Enable_Direct')
-			when 'p'
-				enabled = RocketChat.settings.get('WebRTC_Enable_Private')
-			when 'c'
-				enabled = RocketChat.settings.get('WebRTC_Enable_Channel')
-
-		if enabled is false
-			return
-
-		if not @instancesByRoomId[roomId]?
-			@instancesByRoomId[roomId] = new WebRTCClass Meteor.userId(), roomId
-
-		return @instancesByRoomId[roomId]
-
-
-Meteor.startup ->
-	Tracker.autorun ->
-		if Meteor.userId()
-			RocketChat.Notifications.onUser 'webrtc', (type, data) =>
-				if not data.room? then return
-
-				webrtc = WebRTC.getInstanceByRoomId(data.room)
-
-				webrtc.transport.onUserStream type, data
diff --git a/packages/rocketchat-webrtc/client/WebRTCClass.js b/packages/rocketchat-webrtc/client/WebRTCClass.js
new file mode 100644
index 0000000000000000000000000000000000000000..dfbccbd4391f6c16874c8440c7ebd9e23e8c378b
--- /dev/null
+++ b/packages/rocketchat-webrtc/client/WebRTCClass.js
@@ -0,0 +1,992 @@
+/* globals chrome, ChromeScreenShare */
+class WebRTCTransportClass {
+	constructor(webrtcInstance) {
+		this.debug = false;
+		this.webrtcInstance = webrtcInstance;
+		this.callbacks = {};
+		RocketChat.Notifications.onRoom(this.webrtcInstance.room, 'webrtc', (type, data) => {
+			const onRemoteStatus = this.callbacks['onRemoteStatus'];
+			this.log('WebRTCTransportClass - onRoom', type, data);
+			switch (type) {
+				case 'status':
+					if (onRemoteStatus && onRemoteStatus.length) {
+						onRemoteStatus.forEach(fn => fn(data));
+					}
+			}
+		});
+	}
+
+	log() {
+		if (this.debug === true) {
+			console.log.apply(console, arguments);
+		}
+	}
+
+	onUserStream(type, data) {
+		if (data.room !== this.webrtcInstance.room) {
+			return;
+		}
+		this.log('WebRTCTransportClass - onUser', type, data);
+		const onRemoteCall = this.callbacks['onRemoteCall'];
+		const onRemoteJoin = this.callbacks['onRemoteJoin'];
+		const onRemoteCandidate = this.callbacks['onRemoteCandidate'];
+		const onRemoteDescription = this.callbacks['onRemoteDescription'];
+
+		switch (type) {
+			case 'call':
+				if (onRemoteCall && onRemoteCall.length) {
+					onRemoteCall.forEach(fn => fn(data));
+				}
+				break;
+			case 'join':
+				if (onRemoteJoin && onRemoteJoin.length) {
+					onRemoteJoin.forEach(fn => fn(data));
+				}
+				break;
+			case 'candidate':
+				if (onRemoteCandidate && onRemoteCandidate.length) {
+					onRemoteCandidate.forEach(fn => fn(data));
+				}
+				break;
+			case 'description':
+				if (onRemoteDescription && onRemoteDescription.length) {
+					onRemoteDescription.forEach(fn => fn(data));
+				}
+		}
+	}
+
+	startCall(data) {
+		this.log('WebRTCTransportClass - startCall', this.webrtcInstance.room, this.webrtcInstance.selfId);
+		RocketChat.Notifications.notifyUsersOfRoom(this.webrtcInstance.room, 'webrtc', 'call', {
+			from: this.webrtcInstance.selfId,
+			room: this.webrtcInstance.room,
+			media: data.media,
+			monitor: data.monitor
+		});
+	}
+
+	joinCall(data) {
+		this.log('WebRTCTransportClass - joinCall', this.webrtcInstance.room, this.webrtcInstance.selfId);
+		if (data.monitor === true) {
+			RocketChat.Notifications.notifyUser(data.to, 'webrtc', 'join', {
+				from: this.webrtcInstance.selfId,
+				room: this.webrtcInstance.room,
+				media: data.media,
+				monitor: data.monitor
+			});
+		} else {
+			RocketChat.Notifications.notifyUsersOfRoom(this.webrtcInstance.room, 'webrtc', 'join', {
+				from: this.webrtcInstance.selfId,
+				room: this.webrtcInstance.room,
+				media: data.media,
+				monitor: data.monitor
+			});
+		}
+	}
+
+	sendCandidate(data) {
+		data.from = this.webrtcInstance.selfId;
+		data.room = this.webrtcInstance.room;
+		this.log('WebRTCTransportClass - sendCandidate', data);
+		RocketChat.Notifications.notifyUser(data.to, 'webrtc', 'candidate', data);
+	}
+
+	sendDescription(data) {
+		data.from = this.webrtcInstance.selfId;
+		data.room = this.webrtcInstance.room;
+		this.log('WebRTCTransportClass - sendDescription', data);
+		RocketChat.Notifications.notifyUser(data.to, 'webrtc', 'description', data);
+	}
+
+	sendStatus(data) {
+		this.log('WebRTCTransportClass - sendStatus', data, this.webrtcInstance.room);
+		data.from = this.webrtcInstance.selfId;
+		RocketChat.Notifications.notifyRoom(this.webrtcInstance.room, 'webrtc', 'status', data);
+	}
+
+	onRemoteCall(fn) {
+		const callbacks = this.callbacks;
+		if (callbacks['onRemoteCall'] == null) {
+			callbacks['onRemoteCall'] = [];
+		}
+		callbacks['onRemoteCall'].push(fn);
+	}
+
+	onRemoteJoin(fn) {
+		const callbacks = this.callbacks;
+		if (callbacks['onRemoteJoin'] == null) {
+			callbacks['onRemoteJoin'] = [];
+		}
+		callbacks['onRemoteJoin'].push(fn);
+	}
+
+	onRemoteCandidate(fn) {
+		const callbacks = this.callbacks;
+		if (callbacks['onRemoteCandidate'] == null) {
+			callbacks['onRemoteCandidate'] = [];
+		}
+		callbacks['onRemoteCandidate'].push(fn);
+	}
+
+	onRemoteDescription(fn) {
+		const callbacks = this.callbacks;
+		if (callbacks['onRemoteDescription'] == null) {
+			callbacks['onRemoteDescription'] = [];
+		}
+		callbacks['onRemoteDescription'].push(fn);
+	}
+
+	onRemoteStatus(fn) {
+		const callbacks = this.callbacks;
+		if (callbacks['onRemoteStatus'] == null) {
+			callbacks['onRemoteStatus'] = [];
+		}
+		callbacks['onRemoteStatus'].push(fn);
+	}
+
+
+
+}
+
+class WebRTCClass {
+  /*
+  		@param seldId {String}
+  		@param room {String}
+   */
+
+	constructor(selfId, room) {
+		this.config = {
+			iceServers: []
+		};
+		this.debug = false;
+		this.TransportClass = WebRTCTransportClass;
+		this.selfId = selfId;
+		this.room = room;
+		let servers = RocketChat.settings.get('WebRTC_Servers');
+		if (servers && servers.trim() !== '') {
+			servers = servers.replace(/\s/g, '');
+			servers = servers.split(',');
+
+			servers.forEach(server => {
+				server = server.split('@');
+				const serverConfig = {
+					urls: server.pop()
+				};
+				if (server.length === 1) {
+					server = server[0].split(':');
+					serverConfig.username = decodeURIComponent(server[0]);
+					serverConfig.credential = decodeURIComponent(server[1]);
+				}
+				this.config.iceServers.push(serverConfig);
+			});
+		}
+		this.peerConnections = {};
+		this.remoteItems = new ReactiveVar([]);
+		this.remoteItemsById = new ReactiveVar({});
+		this.callInProgress = new ReactiveVar(false);
+		this.audioEnabled = new ReactiveVar(true);
+		this.videoEnabled = new ReactiveVar(true);
+		this.overlayEnabled = new ReactiveVar(false);
+		this.screenShareEnabled = new ReactiveVar(false);
+		this.localUrl = new ReactiveVar;
+		this.active = false;
+		this.remoteMonitoring = false;
+		this.monitor = false;
+		this.autoAccept = false;
+		this.navigator = undefined;
+		const userAgent = navigator.userAgent.toLocaleLowerCase();
+
+		if (userAgent.indexOf('electron') !== -1) {
+			this.navigator = 'electron';
+		} else if (userAgent.indexOf('chrome') !== -1) {
+			this.navigator = 'chrome';
+		} else if (userAgent.indexOf('firefox') !== -1) {
+			this.navigator = 'firefox';
+		} else if (userAgent.indexOf('safari') !== -1) {
+			this.navigator = 'safari';
+		}
+		const nav = this.navigator;
+		this.screenShareAvailable = nav === 'chrome' || nav === 'firefox' || nav === 'electron';
+		this.media = {
+			video: false,
+			audio: true
+		};
+		this.transport = new this.TransportClass(this);
+		this.transport.onRemoteCall(this.onRemoteCall.bind(this));
+		this.transport.onRemoteJoin(this.onRemoteJoin.bind(this));
+		this.transport.onRemoteCandidate(this.onRemoteCandidate.bind(this));
+		this.transport.onRemoteDescription(this.onRemoteDescription.bind(this));
+		this.transport.onRemoteStatus(this.onRemoteStatus.bind(this));
+		Meteor.setInterval(this.checkPeerConnections.bind(this), 1000);
+
+		//Meteor.setInterval(this.broadcastStatus.bind(@), 1000);
+	}
+
+	log() {
+		if (this.debug === true) {
+			console.log.apply(console, arguments);
+		}
+	}
+
+	onError() {
+		console.error.apply(console, arguments);
+	}
+
+	checkPeerConnections() {
+		const peerConnections = this.peerConnections;
+		Object.keys(peerConnections).forEach(id => {
+			const peerConnection = peerConnections[id];
+			if (peerConnection.iceConnectionState !== 'connected' && peerConnection.iceConnectionState !== 'completed' && peerConnection.createdAt + 5000 < Date.now()) {
+				this.stopPeerConnection(id);
+			}
+		});
+	}
+
+	updateRemoteItems() {
+		const items = [];
+		const itemsById = {};
+		const peerConnections = this.peerConnections;
+
+		Object.keys(peerConnections).forEach(id => {
+			const peerConnection = peerConnections[id];
+
+			peerConnection.getRemoteStreams().forEach(remoteStream => {
+				const item = {
+					id,
+					url: URL.createObjectURL(remoteStream),
+					state: peerConnection.iceConnectionState
+				};
+				switch (peerConnection.iceConnectionState) {
+					case 'checking':
+						item.stateText = 'Connecting...';
+						break;
+					case 'connected':
+					case 'completed':
+						item.stateText = 'Connected';
+						item.connected = true;
+						break;
+					case 'disconnected':
+						item.stateText = 'Disconnected';
+						break;
+					case 'failed':
+						item.stateText = 'Failed';
+						break;
+					case 'closed':
+						item.stateText = 'Closed';
+				}
+				items.push(item);
+				itemsById[id] = item;
+			});
+		});
+		this.remoteItems.set(items);
+		this.remoteItemsById.set(itemsById);
+	}
+
+	resetCallInProgress() {
+		this.callInProgress.set(false);
+	}
+
+	broadcastStatus() {
+		if (this.active !== true || this.monitor === true || this.remoteMonitoring === true) {
+			return;
+		}
+		const remoteConnections = [];
+		const peerConnections = this.peerConnections;
+		Object.keys(peerConnections).forEach(id => {
+			const peerConnection = peerConnections[id];
+			remoteConnections.push({
+				id,
+				media: peerConnection.remoteMedia
+			});
+		});
+
+		this.transport.sendStatus({
+			media: this.media,
+			remoteConnections
+		});
+	}
+
+
+  /*
+  		@param data {Object}
+  			from {String}
+  			media {Object}
+  			remoteConnections {Array[Object]}
+  				id {String}
+  				media {Object}
+   */
+
+	onRemoteStatus(data) {
+		//this.log(onRemoteStatus, arguments);
+		this.callInProgress.set(true);
+		Meteor.clearTimeout(this.callInProgressTimeout);
+		this.callInProgressTimeout = Meteor.setTimeout(this.resetCallInProgress.bind(this), 2000);
+		if (this.active !== true) {
+			return;
+		}
+		const remoteConnections = [{
+			id: data.from,
+			media: data.media
+		},
+			...data.remoteConnections];
+
+		remoteConnections.forEach(remoteConnection => {
+			if (remoteConnection.id !== this.selfId && (this.peerConnections[remoteConnection.id] == null)) {
+				this.log('reconnecting with', remoteConnection.id);
+				this.onRemoteJoin({
+					from: remoteConnection.id,
+					media: remoteConnection.media
+				});
+			}
+		});
+	}
+
+
+  /*
+  		@param id {String}
+   */
+
+	getPeerConnection(id) {
+		if (this.peerConnections[id] != null) {
+			return this.peerConnections[id];
+		}
+		const peerConnection = new RTCPeerConnection(this.config);
+
+		peerConnection.createdAt = Date.now();
+		peerConnection.remoteMedia = {};
+		this.peerConnections[id] = peerConnection;
+		const eventNames = ['icecandidate', 'addstream', 'removestream', 'iceconnectionstatechange', 'datachannel', 'identityresult', 'idpassertionerror', 'idpvalidationerror', 'negotiationneeded', 'peeridentity', 'signalingstatechange'];
+
+		eventNames.forEach(eventName => {
+			peerConnection.addEventListener(eventName, (e) => {
+				this.log(id, e.type, e);
+			});
+		});
+
+		peerConnection.addEventListener('icecandidate', (e) => {
+			if (e.candidate == null) {
+				return;
+			}
+			this.transport.sendCandidate({
+				to: id,
+				candidate: {
+					candidate: e.candidate.candidate,
+					sdpMLineIndex: e.candidate.sdpMLineIndex,
+					sdpMid: e.candidate.sdpMid
+				}
+			});
+		});
+		peerConnection.addEventListener('addstream', () => {
+			this.updateRemoteItems();
+		});
+		peerConnection.addEventListener('removestream', () => {
+			this.updateRemoteItems();
+		});
+		peerConnection.addEventListener('iceconnectionstatechange', () => {
+			if ((peerConnection.iceConnectionState === 'disconnected' || peerConnection.iceConnectionState === 'closed') && peerConnection === this.peerConnections[id]) {
+				this.stopPeerConnection(id);
+				Meteor.setTimeout(() => {
+					if (Object.keys(this.peerConnections).length === 0) {
+						this.stop();
+					}
+				}, 3000);
+			}
+			this.updateRemoteItems();
+		});
+		return peerConnection;
+	}
+
+	_getUserMedia(media, onSuccess, onError) {
+		const onSuccessLocal = function(stream) {
+			if (AudioContext && stream.getAudioTracks().length > 0) {
+				const audioContext = new AudioContext;
+				const source = audioContext.createMediaStreamSource(stream);
+				const volume = audioContext.createGain();
+				source.connect(volume);
+				const peer = audioContext.createMediaStreamDestination();
+				volume.connect(peer);
+				volume.gain.value = 0.6;
+				stream.removeTrack(stream.getAudioTracks()[0]);
+				stream.addTrack(peer.stream.getAudioTracks()[0]);
+				stream.volume = volume;
+				this.audioContext = audioContext;
+			}
+			onSuccess(stream);
+		};
+		navigator.getUserMedia(media, onSuccessLocal, onError);
+	}
+
+	getUserMedia(media, onSuccess, onError = this.onError) {
+		if (media.desktop !== true) {
+			this._getUserMedia(media, onSuccess, onError);
+			return;
+		}
+		if (this.screenShareAvailable !== true) {
+			console.log('Screen share is not avaliable');
+			return;
+		}
+		const getScreen = (audioStream) => {
+			if (document.cookie.indexOf('rocketchatscreenshare=chrome') === -1 && (window.rocketchatscreenshare == null) && this.navigator !== 'electron') {
+				const refresh = function() {
+					swal({
+						type: 'warning',
+						title: TAPi18n.__('Refresh_your_page_after_install_to_enable_screen_sharing')
+					});
+				};
+				swal({
+					type: 'warning',
+					title: TAPi18n.__('Screen_Share'),
+					text: TAPi18n.__('You_need_install_an_extension_to_allow_screen_sharing'),
+					html: true,
+					showCancelButton: true,
+					confirmButtonText: TAPi18n.__('Install_Extension'),
+					cancelButtonText: TAPi18n.__('Cancel')
+				}, (isConfirm) => {
+					if (isConfirm) {
+						if (this.navigator === 'chrome') {
+							const url = 'https://chrome.google.com/webstore/detail/rocketchat-screen-share/nocfbnnmjnndkbipkabodnheejiegccf';
+							try {
+								chrome.webstore.install(url, refresh, function() {
+									window.open(url);
+									refresh();
+								});
+							} catch (_error) {
+								console.log(_error);
+							}
+						} else if (this.navigator === 'firefox') {
+							window.open('https://addons.mozilla.org/en-GB/firefox/addon/rocketchat-screen-share/');
+							refresh();
+						}
+					}
+				});
+				return onError(false);
+			}
+			const getScreenSuccess = (stream) => {
+				if (audioStream != null) {
+					stream.addTrack(audioStream.getAudioTracks()[0]);
+				}
+				onSuccess(stream);
+			};
+			if (this.navigator === 'firefox') {
+				media = {
+					audio: media.audio,
+					video: {
+						mozMediaSource: 'window',
+						mediaSource: 'window'
+					}
+				};
+				this._getUserMedia(media, getScreenSuccess, onError);
+			} else {
+				ChromeScreenShare.getSourceId(this.navigator, (id) => {
+					media = {
+						audio: false,
+						video: {
+							mandatory: {
+								chromeMediaSource: 'desktop',
+								chromeMediaSourceId: id,
+								maxWidth: 1280,
+								maxHeight: 720
+							}
+						}
+					};
+					this._getUserMedia(media, getScreenSuccess, onError);
+				});
+			}
+		};
+		if (this.navigator === 'firefox' || (media.audio == null) || media.audio === false) {
+			getScreen();
+		} else {
+			const getAudioSuccess = (audioStream) => {
+				getScreen(audioStream);
+			};
+			const getAudioError = () => {
+				getScreen();
+			};
+
+			this._getUserMedia({
+				audio: media.audio
+			}, getAudioSuccess, getAudioError);
+		}
+	}
+
+
+  /*
+  		@param callback {Function}
+   */
+
+	getLocalUserMedia(callback) {
+		this.log('getLocalUserMedia', arguments);
+		if (this.localStream != null) {
+			return callback(null, this.localStream);
+		}
+		const onSuccess = (stream) => {
+			this.localStream = stream;
+			this.localUrl.set(URL.createObjectURL(stream));
+			this.videoEnabled.set(this.media.video === true);
+			this.audioEnabled.set(this.media.audio === true);
+			const peerConnections = this.peerConnections;
+			Object.keys(peerConnections).forEach(id => {
+				const peerConnection = peerConnections[id];
+				peerConnection.addStream(stream);
+			});
+			callback(null, this.localStream);
+		};
+		const onError = (error) => {
+			callback(false);
+			this.onError(error);
+		};
+		this.getUserMedia(this.media, onSuccess, onError);
+	}
+
+
+  /*
+  		@param id {String}
+   */
+
+	stopPeerConnection(id) {
+		const peerConnection = this.peerConnections[id];
+		if (peerConnection == null) {
+			return;
+		}
+		delete this.peerConnections[id];
+		peerConnection.close();
+		this.updateRemoteItems();
+	}
+
+	stopAllPeerConnections() {
+		const peerConnections = this.peerConnections;
+
+		Object.keys(peerConnections).forEach(id => {
+			this.stopPeerConnection(id);
+		});
+
+		window.audioContext && window.audioContext.close();
+	}
+
+	setAudioEnabled(enabled = true) {
+		if (this.localStream != null) {
+			if (enabled === true && this.media.audio !== true) {
+				delete this.localStream;
+				this.media.audio = true;
+				this.getLocalUserMedia(() => {
+					this.stopAllPeerConnections();
+					this.joinCall();
+				});
+			} else {
+				this.localStream.getAudioTracks().forEach(function(audio) {
+					audio.enabled = enabled;
+				});
+				this.audioEnabled.set(enabled);
+			}
+		}
+	}
+
+	disableAudio() {
+		this.setAudioEnabled(false);
+	}
+
+	enableAudio() {
+		this.setAudioEnabled(true);
+	}
+
+	setVideoEnabled(enabled = true) {
+		if (this.localStream != null) {
+			if (enabled === true && this.media.video !== true) {
+				delete this.localStream;
+				this.media.video = true;
+				this.getLocalUserMedia(() => {
+					this.stopAllPeerConnections();
+					this.joinCall();
+				});
+			} else {
+				this.localStream.getVideoTracks().forEach(function(video) {
+					video.enabled = enabled;
+				});
+				this.videoEnabled.set(enabled);
+			}
+		}
+	}
+
+	disableScreenShare() {
+		this.setScreenShareEnabled(false);
+	}
+
+	enableScreenShare() {
+		this.setScreenShareEnabled(true);
+	}
+
+	setScreenShareEnabled(enabled = true) {
+		if (this.localStream != null) {
+			this.media.desktop = enabled;
+			delete this.localStream;
+			this.getLocalUserMedia(err => {
+				if (err != null) {
+					return;
+				}
+				this.screenShareEnabled.set(enabled);
+				this.stopAllPeerConnections();
+				this.joinCall();
+			});
+		}
+	}
+
+	disableVideo() {
+		this.setVideoEnabled(false);
+	}
+
+	enableVideo() {
+		this.setVideoEnabled(true);
+	}
+
+	stop() {
+		this.active = false;
+		this.monitor = false;
+		this.remoteMonitoring = false;
+		if (this.localStream != null && typeof this.localStream !== 'undefined') {
+			this.localStream.getTracks().forEach(track => track.stop());
+		}
+		this.localUrl.set(undefined);
+		delete this.localStream;
+		this.stopAllPeerConnections();
+	}
+
+
+  /*
+  		@param media {Object}
+  			audio {Boolean}
+  			video {Boolean}
+   */
+
+	startCall(media = {}) {
+		this.log('startCall', arguments);
+		this.media = media;
+		this.getLocalUserMedia(() => {
+			this.active = true;
+			this.transport.startCall({
+				media: this.media
+			});
+		});
+	}
+
+	startCallAsMonitor(media = {}) {
+		this.log('startCallAsMonitor', arguments);
+		this.media = media;
+		this.active = true;
+		this.monitor = true;
+		this.transport.startCall({
+			media: this.media,
+			monitor: true
+		});
+	}
+
+
+  /*
+  		@param data {Object}
+  			from {String}
+  			monitor {Boolean}
+  			media {Object}
+  				audio {Boolean}
+  				video {Boolean}
+   */
+
+	onRemoteCall(data) {
+		if (this.autoAccept === true) {
+			FlowRouter.goToRoomById(data.room);
+			Meteor.defer(() => {
+				this.joinCall({
+					to: data.from,
+					monitor: data.monitor,
+					media: data.media
+				});
+			});
+			return;
+		}
+
+		const user = Meteor.users.findOne(data.from);
+		let fromUsername = undefined;
+		if (user && user.username) {
+			fromUsername = user.username;
+		}
+		const subscription = ChatSubscription.findOne({
+			rid: data.room
+		});
+
+		let icon;
+		let title;
+		if (data.monitor === true) {
+			icon = 'eye';
+			title = `Monitor call from ${ fromUsername }`;
+		} else if (subscription && subscription.t === 'd') {
+			if (data.media && data.media.video) {
+				icon = 'videocam';
+				title = `Direct video call from ${ fromUsername }`;
+			} else {
+				icon = 'phone';
+				title = `Direct audio call from ${ fromUsername }`;
+			}
+		} else if (data.media && data.media.video) {
+			icon = 'videocam';
+			title = `Group video call from ${ subscription.name }`;
+		} else {
+			icon = 'phone';
+			title = `Group audio call from ${ subscription.name }`;
+		}
+		swal({
+			title: `<i class='icon-${ icon } alert-icon success-color'></i>${ title }`,
+			text: 'Do you want to accept?',
+			html: true,
+			showCancelButton: true,
+			confirmButtonText: 'Yes',
+			cancelButtonText: 'No'
+		}, (isConfirm) => {
+			if (isConfirm) {
+				FlowRouter.goToRoomById(data.room);
+				Meteor.defer(() => {
+					this.joinCall({
+						to: data.from,
+						monitor: data.monitor,
+						media: data.media
+					});
+				});
+			} else {
+				this.stop();
+			}
+		});
+	}
+
+
+  /*
+  		@param data {Object}
+  			to {String}
+  			monitor {Boolean}
+  			media {Object}
+  				audio {Boolean}
+  				video {Boolean}
+  				desktop {Boolean}
+   */
+
+	joinCall(data = {}) {
+		if (data.media && data.media.audio) {
+			this.media.audio = data.media.audio;
+		}
+		if (data.media && data.media.video) {
+			this.media.video = data.media.video;
+		}
+		data.media = this.media;
+		this.log('joinCall', arguments);
+		this.getLocalUserMedia(() => {
+			this.remoteMonitoring = data.monitor;
+			this.active = true;
+			this.transport.joinCall(data);
+		});
+	}
+
+
+	onRemoteJoin(data) {
+		if (this.active !== true) {
+			return;
+		}
+		this.log('onRemoteJoin', arguments);
+		let peerConnection = this.getPeerConnection(data.from);
+
+		// needsRefresh = false
+		// if peerConnection.iceConnectionState isnt 'new'
+		// needsAudio = data.media.audio is true and peerConnection.remoteMedia.audio isnt true
+		// needsVideo = data.media.video is true and peerConnection.remoteMedia.video isnt true
+		// needsRefresh = needsAudio or needsVideo or data.media.desktop isnt peerConnection.remoteMedia.desktop
+
+		// # if peerConnection.signalingState is "have-local-offer" or needsRefresh
+
+		if (peerConnection.signalingState !== 'checking') {
+			this.stopPeerConnection(data.from);
+			peerConnection = this.getPeerConnection(data.from);
+		}
+		if (peerConnection.iceConnectionState !== 'new') {
+			return;
+		}
+		peerConnection.remoteMedia = data.media;
+		if (this.localStream) {
+			peerConnection.addStream(this.localStream);
+		}
+		const onOffer = offer => {
+			const onLocalDescription = () => {
+				this.transport.sendDescription({
+					to: data.from,
+					type: 'offer',
+					ts: peerConnection.createdAt,
+					media: this.media,
+					description: {
+						sdp: offer.sdp,
+						type: offer.type
+					}
+				});
+			};
+
+			peerConnection.setLocalDescription(new RTCSessionDescription(offer), onLocalDescription, this.onError);
+		};
+
+		if (data.monitor === true) {
+			peerConnection.createOffer(onOffer, this.onError, {
+				mandatory: {
+					OfferToReceiveAudio: data.media.audio,
+					OfferToReceiveVideo: data.media.video
+				}
+			});
+		} else {
+			peerConnection.createOffer(onOffer, this.onError);
+		}
+	}
+
+
+
+	onRemoteOffer(data) {
+		if (this.active !== true) {
+			return;
+		}
+
+		this.log('onRemoteOffer', arguments);
+		let peerConnection = this.getPeerConnection(data.from);
+
+		if (['have-local-offer', 'stable'].includes(peerConnection.signalingState) && (peerConnection.createdAt < data.ts)) {
+			this.stopPeerConnection(data.from);
+			peerConnection = this.getPeerConnection(data.from);
+		}
+
+		if (peerConnection.iceConnectionState !== 'new') {
+			return;
+		}
+
+		peerConnection.setRemoteDescription(new RTCSessionDescription(data.description));
+
+		try {
+			if (this.localStream) {
+				peerConnection.addStream(this.localStream);
+			}
+		} catch (error) {
+			console.log(error);
+		}
+
+		const onAnswer = answer => {
+			const onLocalDescription = () => {
+				this.transport.sendDescription({
+					to: data.from,
+					type: 'answer',
+					ts: peerConnection.createdAt,
+					description: {
+						sdp: answer.sdp,
+						type: answer.type
+					}
+				});
+			};
+
+			peerConnection.setLocalDescription(new RTCSessionDescription(answer), onLocalDescription, this.onError);
+		};
+
+		peerConnection.createAnswer(onAnswer, this.onError);
+	}
+
+
+  /*
+  		@param data {Object}
+  			to {String}
+  			from {String}
+  			candidate {RTCIceCandidate JSON encoded}
+   */
+
+	onRemoteCandidate(data) {
+		if (this.active !== true) {
+			return;
+		}
+		if (data.to !== this.selfId) {
+			return;
+		}
+		this.log('onRemoteCandidate', arguments);
+		const peerConnection = this.getPeerConnection(data.from);
+		if (peerConnection.iceConnectionState !== 'closed' && peerConnection.iceConnectionState !== 'failed' && peerConnection.iceConnectionState !== 'disconnected' && peerConnection.iceConnectionState !== 'completed') {
+			peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate));
+		}
+	}
+
+
+  /*
+  		@param data {Object}
+  			to {String}
+  			from {String}
+  			type {String} [offer, answer]
+  			description {RTCSessionDescription JSON encoded}
+  			ts {Integer}
+  			media {Object}
+  				audio {Boolean}
+  				video {Boolean}
+  				desktop {Boolean}
+   */
+
+	onRemoteDescription(data) {
+		if (this.active !== true) {
+			return;
+		}
+		if (data.to !== this.selfId) {
+			return;
+		}
+		this.log('onRemoteDescription', arguments);
+		const peerConnection = this.getPeerConnection(data.from);
+		if (data.type === 'offer') {
+			peerConnection.remoteMedia = data.media;
+			this.onRemoteOffer({
+				from: data.from,
+				ts: data.ts,
+				description: data.description
+			});
+		} else {
+			peerConnection.setRemoteDescription(new RTCSessionDescription(data.description));
+		}
+	}
+
+}
+
+const WebRTC = new class {
+	constructor() {
+		this.instancesByRoomId = {};
+	}
+
+	getInstanceByRoomId(roomId) {
+		const subscription = ChatSubscription.findOne({ rid: roomId });
+		if (!subscription) {
+			return;
+		}
+		let enabled = false;
+		switch (subscription.t) {
+			case 'd':
+				enabled = RocketChat.settings.get('WebRTC_Enable_Direct');
+				break;
+			case 'p':
+				enabled = RocketChat.settings.get('WebRTC_Enable_Private');
+				break;
+			case 'c':
+				enabled = RocketChat.settings.get('WebRTC_Enable_Channel');
+		}
+		if (enabled === false) {
+			return;
+		}
+		if (this.instancesByRoomId[roomId] == null) {
+			this.instancesByRoomId[roomId] = new WebRTCClass(Meteor.userId(), roomId);
+		}
+		return this.instancesByRoomId[roomId];
+	}
+};
+
+Meteor.startup(function() {
+	Tracker.autorun(function() {
+		if (Meteor.userId()) {
+			RocketChat.Notifications.onUser('webrtc', (type, data) => {
+				if (data.room == null) {
+					return;
+				}
+				const webrtc = WebRTC.getInstanceByRoomId(data.room);
+				webrtc.transport.onUserStream(type, data);
+			});
+		}
+	});
+});
+
+export {WebRTC};
diff --git a/packages/rocketchat-webrtc/adapter.js b/packages/rocketchat-webrtc/client/adapter.js
similarity index 100%
rename from packages/rocketchat-webrtc/adapter.js
rename to packages/rocketchat-webrtc/client/adapter.js
diff --git a/packages/rocketchat-webrtc/client/screenShare.js b/packages/rocketchat-webrtc/client/screenShare.js
new file mode 100644
index 0000000000000000000000000000000000000000..22f9ba2dc951e3b506f2003f104ea44499d67e4a
--- /dev/null
+++ b/packages/rocketchat-webrtc/client/screenShare.js
@@ -0,0 +1,29 @@
+/* globals ChromeScreenShare, fireGlobalEvent */
+this.ChromeScreenShare = {
+	screenCallback: undefined,
+	getSourceId(navigator, callback) {
+		if (callback == null) {
+			throw '"callback" parameter is mandatory.';
+		}
+		ChromeScreenShare.screenCallback = callback;
+		if (navigator === 'electron') {
+			return fireGlobalEvent('get-sourceId', '*');
+		}
+		return window.postMessage('get-sourceId', '*');
+	}
+};
+
+window.addEventListener('message', function(e) {
+	if (e.origin !== window.location.origin) {
+		return;
+	}
+	if (e.data === 'PermissionDeniedError') {
+		if (ChromeScreenShare.screenCallback != null) {
+			return ChromeScreenShare.screenCallback('PermissionDeniedError');
+		}
+		throw new Error('PermissionDeniedError');
+	}
+	if (e.data.sourceId != null) {
+		return typeof ChromeScreenShare.screenCallback === 'function' && ChromeScreenShare.screenCallback(e.data.sourceId);
+	}
+});
diff --git a/packages/rocketchat-webrtc/package.js b/packages/rocketchat-webrtc/package.js
index 53d56d1cde7020237cae4eecaf24cea53909d350..608f3d91768ef973a71a229d4fea6089851e4a18 100644
--- a/packages/rocketchat-webrtc/package.js
+++ b/packages/rocketchat-webrtc/package.js
@@ -7,16 +7,15 @@ Package.describe({
 
 Package.onUse(function(api) {
 	api.use('rocketchat:lib');
-	api.use('coffeescript');
 	api.use('ecmascript');
 
 	api.use('templating', 'client');
+	api.mainModule('client/WebRTCClass.js', 'client');
+	api.addFiles('client/adapter.js', 'client');
+	// api.addFiles(');
+	api.addFiles('client/screenShare.js', 'client');
 
-	api.addFiles('adapter.js', 'client');
-	api.addFiles('WebRTCClass.coffee', 'client');
-	api.addFiles('screenShare.coffee', 'client');
+	api.addFiles('server/settings.js', 'server');
 
-	api.addFiles('server/settings.coffee', 'server');
-
-	api.export('WebRTC');
+	api.export('WebRTC', 'client');
 });
diff --git a/packages/rocketchat-webrtc/screenShare.coffee b/packages/rocketchat-webrtc/screenShare.coffee
deleted file mode 100644
index c157e7c045b3eaa5c44e28f8c7541bbf3073e764..0000000000000000000000000000000000000000
--- a/packages/rocketchat-webrtc/screenShare.coffee
+++ /dev/null
@@ -1,27 +0,0 @@
-@ChromeScreenShare =
-	screenCallback: undefined
-
-	getSourceId: (navigator, callback) ->
-		if not callback? then throw '"callback" parameter is mandatory.'
-
-		ChromeScreenShare.screenCallback = callback
-
-		if navigator is 'electron'
-			fireGlobalEvent('get-sourceId', '*')
-		else
-			window.postMessage('get-sourceId', '*')
-
-window.addEventListener 'message', (e) ->
-	if e.origin isnt window.location.origin
-		return
-
-	# "cancel" button was clicked
-	if e.data is 'PermissionDeniedError'
-		if ChromeScreenShare.screenCallback?
-			return ChromeScreenShare.screenCallback('PermissionDeniedError')
-		else
-			throw new Error('PermissionDeniedError')
-
-	# extension shared temp sourceId
-	if e.data.sourceId?
-		ChromeScreenShare.screenCallback?(e.data.sourceId)
diff --git a/packages/rocketchat-webrtc/server/settings.coffee b/packages/rocketchat-webrtc/server/settings.coffee
deleted file mode 100644
index 84337ffbc49c9bab26aeaee854b463f412ec6ac7..0000000000000000000000000000000000000000
--- a/packages/rocketchat-webrtc/server/settings.coffee
+++ /dev/null
@@ -1,5 +0,0 @@
-RocketChat.settings.addGroup 'WebRTC', ->
-	@add 'WebRTC_Enable_Channel', false, { type: 'boolean', group: 'WebRTC', public: true}
-	@add 'WebRTC_Enable_Private', true , { type: 'boolean', group: 'WebRTC', public: true}
-	@add 'WebRTC_Enable_Direct' , true , { type: 'boolean', group: 'WebRTC', public: true}
-	@add 'WebRTC_Servers', 'stun:stun.l.google.com:19302, stun:23.21.150.121, team%40rocket.chat:demo@turn:numb.viagenie.ca:3478', { type: 'string', group: 'WebRTC', public: true}
diff --git a/packages/rocketchat-webrtc/server/settings.js b/packages/rocketchat-webrtc/server/settings.js
new file mode 100644
index 0000000000000000000000000000000000000000..c32f3103acaf04b62876fa0d16f37d36e19f9490
--- /dev/null
+++ b/packages/rocketchat-webrtc/server/settings.js
@@ -0,0 +1,22 @@
+RocketChat.settings.addGroup('WebRTC', function() {
+	this.add('WebRTC_Enable_Channel', false, {
+		type: 'boolean',
+		group: 'WebRTC',
+		'public': true
+	});
+	this.add('WebRTC_Enable_Private', true, {
+		type: 'boolean',
+		group: 'WebRTC',
+		'public': true
+	});
+	this.add('WebRTC_Enable_Direct', true, {
+		type: 'boolean',
+		group: 'WebRTC',
+		'public': true
+	});
+	return this.add('WebRTC_Servers', 'stun:stun.l.google.com:19302, stun:23.21.150.121, team%40rocket.chat:demo@turn:numb.viagenie.ca:3478', {
+		type: 'string',
+		group: 'WebRTC',
+		'public': true
+	});
+});
diff --git a/server/configuration/accounts_meld.js b/server/configuration/accounts_meld.js
index 1848635c4a5ea9c1d3633014842e19a1db472bfc..8eee62d4f85589ad7cc5e3826f8555dc01bfafd7 100644
--- a/server/configuration/accounts_meld.js
+++ b/server/configuration/accounts_meld.js
@@ -18,7 +18,8 @@ Accounts.updateOrCreateUserFromExternalService = function(serviceName, serviceDa
 
 	if (serviceName === 'meteor-developer') {
 		if (Array.isArray(serviceData.emails)) {
-			serviceData.email = serviceData.emails.sort(a => a.primary !== true).filter(item => item.verified === true)[0];
+			const primaryEmail = serviceData.emails.sort(a => a.primary !== true).filter(item => item.verified === true)[0];
+			serviceData.email = primaryEmail && primaryEmail.address;
 		}
 	}
 
diff --git a/server/startup/migrations/v094.js b/server/startup/migrations/v094.js
new file mode 100644
index 0000000000000000000000000000000000000000..4cc141cb8d8d81ce17fc5b0cc7ccd12def3a2999
--- /dev/null
+++ b/server/startup/migrations/v094.js
@@ -0,0 +1,26 @@
+RocketChat.Migrations.add({
+	version: 94,
+	up() {
+		const query = {
+			'emails.address.address': { $exists: true }
+		};
+
+		RocketChat.models.Users.find(query, {'emails.address.address': 1}).forEach((user) => {
+			let emailAddress;
+			user.emails.some(email => {
+				if (email.address && email.address.address) {
+					emailAddress = email.address.address;
+					return true;
+				}
+			});
+			RocketChat.models.Users.update({
+				_id: user._id,
+				'emails.address.address': emailAddress
+			}, {
+				$set: {
+					'emails.$.address': emailAddress
+				}
+			});
+		});
+	}
+});
diff --git a/server/startup/presence.js b/server/startup/presence.js
index f8248e9c3d7b77e31db3412a58d37edfe805e538..59d2a3c94a069a488f4b46114cd8752d7d43f21a 100644
--- a/server/startup/presence.js
+++ b/server/startup/presence.js
@@ -3,11 +3,11 @@
 Meteor.startup(function() {
 	const instance = {
 		host: 'localhost',
-		port: process.env.PORT
+		port: String(process.env.PORT).trim()
 	};
 
 	if (process.env.INSTANCE_IP) {
-		instance.host = process.env.INSTANCE_IP;
+		instance.host = String(process.env.INSTANCE_IP).trim();
 	}
 
 	InstanceStatus.registerInstance('rocket.chat', instance);
diff --git a/server/stream/streamBroadcast.js b/server/stream/streamBroadcast.js
index 6de334e34b95565b97435f040812b28d5fcf95fc..321bdc72b54047182e1ebf6afc0b5153083cd401 100644
--- a/server/stream/streamBroadcast.js
+++ b/server/stream/streamBroadcast.js
@@ -2,6 +2,9 @@
 
 import {DDPCommon} from 'meteor/ddp-common';
 
+process.env.PORT = String(process.env.PORT).trim();
+process.env.INSTANCE_IP = String(process.env.INSTANCE_IP).trim();
+
 const connections = {};
 this.connections = connections;
 
@@ -257,3 +260,18 @@ function startStreamBroadcast() {
 Meteor.startup(function() {
 	return startStreamBroadcast();
 });
+
+Meteor.methods({
+	'instances/get'() {
+		if (!RocketChat.authz.hasPermission(Meteor.userId(), 'view-statistics')) {
+			throw new Meteor.Error('error-action-not-allowed', 'List instances is not allowed', {
+				method: 'instances/get'
+			});
+		}
+
+		return Object.keys(connections).map(address => {
+			const conn = connections[address];
+			return Object.assign({ address, currentStatus: conn._stream.currentStatus }, _.pick(conn, 'instanceRecord', 'broadcastAuth'));
+		});
+	}
+});
diff --git a/tests/end-to-end/ui/09-channel.js b/tests/end-to-end/ui/09-channel.js
index 1b7bf7d2870869e3b4e3ceb6cf1424e6a60546a7..2a852d12589c999409113941887ef8d2ce2f8e41 100644
--- a/tests/end-to-end/ui/09-channel.js
+++ b/tests/end-to-end/ui/09-channel.js
@@ -37,6 +37,7 @@ describe('channel', ()=> {
 
 				it('should start a direct message with rocket.cat', () => {
 					sideNav.searchChannel('rocket.cat');
+					mainContent.channelTitle.waitForVisible(5000);
 					mainContent.channelTitle.getText().should.equal('rocket.cat');
 				});
 			});
diff --git a/tests/pageobjects/main-content.page.js b/tests/pageobjects/main-content.page.js
index beee3e77ed56e917e3472d0094fc66d37db2b953..0a672a9eabbe75ff15449e125aa9177284f7c88f 100644
--- a/tests/pageobjects/main-content.page.js
+++ b/tests/pageobjects/main-content.page.js
@@ -70,6 +70,7 @@ class MainContent extends Page {
 		this.setTextToInput(text);
 		this.sendBtn.click();
 		browser.waitUntil(function() {
+			browser.waitForVisible('.message:last-child .body', 5000);
 			return browser.getText('.message:last-child .body') === text;
 		}, 2000);
 	}
@@ -105,18 +106,21 @@ class MainContent extends Page {
 
 	waitForLastMessageTextAttachmentEqualsText(text) {
 		browser.waitUntil(function() {
+			browser.waitForVisible('.message:last-child .attachment-text', 5000);
 			return browser.getText('.message:last-child .attachment-text') === text;
 		}, 2000);
 	}
 
 	waitForLastMessageEqualsText(text) {
 		browser.waitUntil(function() {
+			browser.waitForVisible('.message:last-child .body', 5000);
 			return browser.getText('.message:last-child .body') === text;
 		}, 4000);
 	}
 
 	waitForLastMessageUserEqualsText(text) {
 		browser.waitUntil(function() {
+			browser.waitForVisible('.message:last-child .user-card-message:nth-of-type(2)', 5000);
 			return browser.getText('.message:last-child .user-card-message:nth-of-type(2)') === text;
 		}, 2000);
 	}
diff --git a/tests/pageobjects/side-nav.page.js b/tests/pageobjects/side-nav.page.js
index f83e9a693ea73b8d96a88ad620cdb24a1ab19ed8..a7191a4f4c2beb2fc3711d5eeed506ff997d79f3 100644
--- a/tests/pageobjects/side-nav.page.js
+++ b/tests/pageobjects/side-nav.page.js
@@ -43,6 +43,7 @@ class SideNav extends Page {
 		browser.click(`.rooms-list > .wrapper > ul [title="${ channelName }"]`);
 		this.messageInput.waitForExist(5000);
 		browser.waitUntil(function() {
+			browser.waitForVisible('.room-title', 5000);
 			return browser.getText('.room-title') === channelName;
 		}, 5000);
 	}
@@ -54,6 +55,7 @@ class SideNav extends Page {
 		browser.waitForVisible(`.room-title=${ channelName }`, 10000);
 		browser.click(`.room-title=${ channelName }`);
 		browser.waitUntil(function() {
+			browser.waitForVisible('.room-title', 5000);
 			return browser.getText('.room-title') === channelName;
 		}, 5000);
 	}