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/.meteor/packages b/.meteor/packages
index 45753817ec06de3b628aff1d41a2b2f0c2810adc..9c3ec1031737afe626f26299515963b49da8c251 100644
--- a/.meteor/packages
+++ b/.meteor/packages
@@ -8,15 +8,15 @@ accounts-facebook@1.1.1
 accounts-github@1.2.1
 accounts-google@1.1.2
 accounts-meteor-developer@1.2.1
-accounts-password@1.3.4
+accounts-password@1.3.6
 accounts-twitter@1.2.1
 blaze-html-templates
 check@1.2.5
 coffeescript@1.11.1_4
 ddp-rate-limiter@1.0.7
-ecmascript@0.7.2
+ecmascript@0.7.3
 ejson@1.0.13
-email@1.1.18
+email@1.2.1
 fastclick@1.0.13
 http@1.2.12
 jquery@1.11.10
@@ -24,7 +24,7 @@ less@2.7.9
 logging@1.1.17
 meteor-base@1.0.4
 mobile-experience@1.0.4
-mongo@1.1.16
+mongo@1.1.17
 random@1.0.10
 rate-limit@1.0.8
 reactive-dict@1.1.8
@@ -36,7 +36,7 @@ shell-server@0.2.3
 spacebars
 standard-minifier-css@1.3.4
 standard-minifier-js@2.0.0
-tracker@1.1.2
+tracker@1.1.3
 
 rocketchat:2fa
 rocketchat:action-links
diff --git a/.meteor/release b/.meteor/release
index 605b4e1f03103658e752cf5bad58f52bee90009f..fb6f3bc15e2230094f0495e9d4a67d2bcf537d9d 100644
--- a/.meteor/release
+++ b/.meteor/release
@@ -1 +1 @@
-METEOR@1.4.4.1
+METEOR@1.4.4.2
diff --git a/.meteor/versions b/.meteor/versions
index 0801efdbd9c33d5e7b11ee5f56ec7baadc7af609..67e75bb0fc3d0e429392123df1749c372a7a8764 100644
--- a/.meteor/versions
+++ b/.meteor/versions
@@ -1,10 +1,10 @@
-accounts-base@1.2.16
+accounts-base@1.2.17
 accounts-facebook@1.1.1
 accounts-github@1.2.1
 accounts-google@1.1.2
 accounts-meteor-developer@1.2.1
 accounts-oauth@1.1.15
-accounts-password@1.3.5
+accounts-password@1.3.6
 accounts-twitter@1.2.1
 aldeed:simple-schema@1.5.3
 allow-deny@1.0.5
@@ -81,14 +81,14 @@ meteorhacks:meteorx@1.4.1
 meteorspark:util@0.2.0
 minifier-css@1.2.16
 minifier-js@2.0.0
-minimongo@1.0.21
+minimongo@1.0.23
 mizzao:autocomplete@0.5.1
 mizzao:timesync@0.3.4
 mobile-experience@1.0.4
 mobile-status-bar@1.0.14
 modules@0.8.2
 modules-runtime@0.7.10
-mongo@1.1.16
+mongo@1.1.17
 mongo-id@1.0.6
 mongo-livedata@1.0.12
 mrt:reactive-store@0.0.1
@@ -243,7 +243,7 @@ templating-tools@1.1.2
 tmeasday:crypto-base@3.1.2
 tmeasday:crypto-md5@3.1.2
 todda00:friendly-slugs@0.6.0
-tracker@1.1.2
+tracker@1.1.3
 twitter-oauth@1.2.0
 ui@1.0.13
 underscore@1.0.10
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..7dee556b5b785f8c6745b7bbc5879b696d1080e7
--- /dev/null
+++ b/.scripts/set-version.js
@@ -0,0 +1,48 @@
+/* eslint object-shorthand: 0, prefer-template: 0 */
+
+const path = require('path');
+const fs = require('fs');
+let pkgJson = {};
+
+try {
+	pkgJson = require(path.resolve(
+		process.cwd(),
+		'./package.json'
+	));
+} catch (err) {
+	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',
+	'./.docker/Dockerfile',
+	'./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');
+	});
+});
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/.snapcraft/README.md b/.snapcraft/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..f0197f2dc65bf5e06c9f2b3c30b8790f77bcbb13
--- /dev/null
+++ b/.snapcraft/README.md
@@ -0,0 +1,61 @@
+![Rocket.Chat logo](https://rocket.chat/images/logo/logo-dark.svg?v3)
+
+# rocketchat-server snap for Ubuntu Core  (all arch)
+
+Features:
+* bundles ubuntu distribution specific and RC compatible mongodb version
+* oplog tailing for mongo by default
+* mongodb backup command  
+* mongodb restore command
+* caddy reverse proxy built-in - capable of handling free lestencrypt ssl
+
+Note:
+
+Currently, this repository is mirrored on launchpad, and used to build latest ARMHF and i386 snaps.   
+
+You can download recent builds here:
+https://code.launchpad.net/~sing-li/+snap/rocketchat-server
+
+Due an issue with the existing installed base of amd64 users (existing snap always installed mongodb 3.2  [#issue](https://github.com/RocketChat/rocketchat-server-snap/issues/3)), this snap is not currently used for amd64 builds.
+
+### Test installation 
+
+Download the latest snap file of the corresponding architecture to your Ubuntu Core 16 or 16.04LTS server.
+
+`sudo snap install ./rocketchat-server-xxxxxxxx.snap  --dangerous`
+
+
+### Development or compile your own snap
+
+Make sure you have `snapcraft` installed.
+
+```
+git clone https://github.com/RocketChat/rocketchat-server-snap
+cd rocketchat-server-snap
+snapcraft snap
+```
+
+### Regression tests (run for amd64, i386 and armhf):
+- snapcraft runs properly
+- snap installs properly
+- all services start automatically
+- rc service shows a 5-second restart delay while waiting for mongo
+	- to test manually, stop rc, stop mongo, start rc, wait 20s or so, start mongo
+- rc can be successfully restarted via the "Restart the server" button under Admin > Settings > General
+- rc service shows a 5-second delay when restarted via this button
+- all commands execute successfully:
+	- initcaddy
+		- modify the Caddyfile to test:
+			- self-signed TLS certificate (use the "tls self_signed" caddy directive)
+			- changing ports (with and without TLS)
+			- using IP address (only works without TLS)
+			- successfully acquiring a Let's Encrypt certificate (requires a registered domain)
+	- backupdb
+		- should run only with sudo
+	- restoredb
+		- ideally, stop rc service prior to running this (mongo must be running)
+		- should run only with sudo
+		- use any file outside of $snap_common (should fail)
+		- use the file created with backupdb
+		- use a dummy .tgz file without actual data
+			- with and without a "parties" directory in the archive
diff --git a/.snapcraft/candidate/snapcraft.yaml b/.snapcraft/candidate/snapcraft.yaml
index 4bab82b5b24fd5c205ee482beaa49ed82cf61798..567d2e4790021d44cb13228f34fc80831ac0f2d8 100644
--- a/.snapcraft/candidate/snapcraft.yaml
+++ b/.snapcraft/candidate/snapcraft.yaml
@@ -11,6 +11,7 @@ version: #{RC_VERSION}
 summary: Rocket.Chat server
 description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/
 confinement: strict
+assumes: [snapd2.21]
 apps:
     rocketchat-server:
         command: startRocketChat
@@ -24,6 +25,12 @@ apps:
         command: env LC_ALL=C caddy -conf=$SNAP_DATA/Caddyfile -host=localhost:8080
         daemon: simple
         plugs: [network, network-bind]
+    mongo:
+        command: env LC_ALL=C mongo
+        plugs: [network]
+    restoredb:
+        command: env LC_ALL=C restoredb
+        plugs: [network]
     backupdb:
         command: env LC_ALL=c rcbackup
         plugs: [network]
@@ -48,7 +55,7 @@ parts:
             lib/node_modules: node_modules
     rocketchat-server:
         plugin: dump
-        after: [mongodb]
+        after: [node]
         source: https://rocket.chat/releases/release-candidate/download
         source-type: tar
         stage-packages:
@@ -59,22 +66,15 @@ parts:
             - .node_version.txt
             - usr
             - lib
-        snap:
-            - programs
-            - main.js
-            - .node_version.txt
-            - usr
-            - lib
     mongodb:
-        source: https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-3.2.7.tgz
+        build-packages:
+            - wget
+        source: ./
+        prepare: ./resources/preparemongo
         plugin: dump
         stage-packages:
             - libssl1.0.0
-        stage:
-            - usr
-            - bin
-            - lib
-        snap:
+        prime:
             - usr
             - bin
             - lib
@@ -83,23 +83,20 @@ parts:
         source: resources/
         organize:
             rcbackup: bin/rcbackup
+            restoredb: bin/restoredb
             startmongo: bin/startmongo
             startRocketChat: bin/startRocketChat
             initreplset.js: bin/initreplset.js
             Caddyfile: bin/Caddyfile
             initcaddy: bin/initcaddy
-        snap:
+        prime:
             - bin
     caddy:
-        plugin: go
-        go-importpath:  github.com/mholt/caddy
-        source: https://github.com/mholt/caddy
-        source-type: git
-        source-commit: 53e117802fedd5915eeb32907873d8786a4b2936
-        snap:
-            - bin/caddy
-        after: [go]
-    go:
-        source-tag: go1.8
-        stage:
-        - bin
+        prepare: ./resources/preparecaddy
+        plugin: dump
+        source: ./
+        prime:
+            - bin
+        organize:
+            caddy: bin/caddy
+        after: [mongodb]
diff --git a/.snapcraft/edge/snapcraft.yaml b/.snapcraft/edge/snapcraft.yaml
index 00a0782a212a01e0b708501e794142c70f3b4001..2934d80e5907cc9e77bac8c132df486c19f8fa7f 100644
--- a/.snapcraft/edge/snapcraft.yaml
+++ b/.snapcraft/edge/snapcraft.yaml
@@ -11,6 +11,7 @@ version: #{RC_VERSION}
 summary: Rocket.Chat server
 description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/
 confinement: strict
+assumes: [snapd2.21]
 apps:
     rocketchat-server:
         command: startRocketChat
@@ -24,6 +25,12 @@ apps:
         command: env LC_ALL=C caddy -conf=$SNAP_DATA/Caddyfile -host=localhost:8080
         daemon: simple
         plugs: [network, network-bind]
+    mongo:
+        command: env LC_ALL=C mongo
+        plugs: [network]
+    restoredb:
+        command: env LC_ALL=C restoredb
+        plugs: [network]
     backupdb:
         command: env LC_ALL=c rcbackup
         plugs: [network]
@@ -48,7 +55,7 @@ parts:
             lib/node_modules: node_modules
     rocketchat-server:
         plugin: dump
-        after: [mongodb]
+        after: [node]
         source: https://rocket.chat/releases/develop/download
         source-type: tar
         stage-packages:
@@ -59,22 +66,15 @@ parts:
             - .node_version.txt
             - usr
             - lib
-        snap:
-            - programs
-            - main.js
-            - .node_version.txt
-            - usr
-            - lib
     mongodb:
-        source: https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-3.2.7.tgz
+        build-packages:
+            - wget
+        source: ./
+        prepare: ./resources/preparemongo
         plugin: dump
         stage-packages:
             - libssl1.0.0
-        stage:
-            - usr
-            - bin
-            - lib
-        snap:
+        prime:
             - usr
             - bin
             - lib
@@ -83,23 +83,20 @@ parts:
         source: resources/
         organize:
             rcbackup: bin/rcbackup
+            restoredb: bin/restoredb
             startmongo: bin/startmongo
             startRocketChat: bin/startRocketChat
             initreplset.js: bin/initreplset.js
             Caddyfile: bin/Caddyfile
             initcaddy: bin/initcaddy
-        snap:
+        prime:
             - bin
     caddy:
-        plugin: go
-        go-importpath:  github.com/mholt/caddy
-        source: https://github.com/mholt/caddy
-        source-type: git
-        source-commit: 53e117802fedd5915eeb32907873d8786a4b2936
-        snap:
-            - bin/caddy
-        after: [go]
-    go:
-        source-tag: go1.8
-        stage:
-        - bin
+        prepare: ./resources/preparecaddy
+        plugin: dump
+        source: ./
+        prime:
+            - bin
+        organize:
+            caddy: bin/caddy
+        after: [mongodb]
diff --git a/.snapcraft/resources/preparecaddy b/.snapcraft/resources/preparecaddy
new file mode 100755
index 0000000000000000000000000000000000000000..86d530554c04be8bf519ba3d44d68f0d1d1c60a2
--- /dev/null
+++ b/.snapcraft/resources/preparecaddy
@@ -0,0 +1,36 @@
+#! /bin/bash
+
+caddy_bin="caddy"
+caddy_dl_ext=".tar.gz"
+
+# NOTE: `uname -m` is more accurate and universal than `arch`
+# See https://en.wikipedia.org/wiki/Uname
+unamem="$(uname -m)"
+if [[ $unamem == *aarch64* ]]; then
+        caddy_arch="arm64"
+elif [[ $unamem == *64* ]]; then
+        caddy_arch="amd64"
+elif [[ $unamem == *86* ]]; then
+        caddy_arch="386"
+elif [[ $unamem == *armv5* ]]; then
+        caddy_arch="arm"
+        caddy_arm="5"
+elif [[ $unamem == *armv6l* ]]; then
+        caddy_arch="arm"
+        caddy_arm="6"
+elif [[ $unamem == *armv7l* ]]; then
+        caddy_arch="arm"
+        caddy_arm="7"
+else
+        echo "Aborted, unsupported or unknown architecture: $unamem"
+        return 2
+fi
+
+echo "Downloading Caddy for $caddy_os/$caddy_arch$caddy_arm..."
+caddy_file="caddy_linux_$caddy_arch${caddy_arm}_custom$caddy_dl_ext"
+caddy_url="https://caddyserver.com/download/linux/$caddy_arch$caddy_arm?plugins=$caddy_plugins"
+echo "$caddy_url"
+
+wget --quiet "$caddy_url" -O "$caddy_file"
+tar -xzf $caddy_file -C . "$caddy_bin"
+chmod +x $caddy_bin
diff --git a/.snapcraft/resources/preparemongo b/.snapcraft/resources/preparemongo
new file mode 100755
index 0000000000000000000000000000000000000000..332dd7d46847a0742b5f3014967aa904e02dfc21
--- /dev/null
+++ b/.snapcraft/resources/preparemongo
@@ -0,0 +1,22 @@
+#! /bin/bash
+
+if [[ $(uname -m) == "x86_64" ]]
+then
+    wget --backups=0 "https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-3.2.7.tgz"
+    tar -zxf ./mongodb-linux-x86_64-ubuntu1604-3.2.7.tgz --strip-components=1
+else
+    IFS=" " read -a links <<< $(apt-get -y --print-uris install mongodb | egrep -o "https?://[^']+")
+    for link in ${links[@]}
+    do
+        wget --backups=0 ${link}
+    done
+
+    IFS=" " read -a deb_pkgs <<< $(ls ./ | egrep "\.deb")
+    for pkg in ${deb_pkgs[@]}
+    do
+        echo "Extracting ${pkg}..."
+        dpkg-deb -R ${pkg} ./
+    done
+
+    mv usr/bin bin
+fi
diff --git a/.snapcraft/resources/restoredb b/.snapcraft/resources/restoredb
new file mode 100755
index 0000000000000000000000000000000000000000..0a204e8377097d3d923a30864233267c77e6ff53
--- /dev/null
+++ b/.snapcraft/resources/restoredb
@@ -0,0 +1,72 @@
+#! /bin/bash
+
+if [[ ${EUID} != 0 ]]
+then
+    echo "[-] This task must be run with 'sudo'."
+    exit
+fi
+
+backup_file=${1}
+if [[ ! -f ${backup_file} ]]
+then
+    echo "[-] Usage: snap run rocketchat-server.restoredb ${SNAP_COMMON}/backup_file.tgz"
+    exit
+fi
+
+cd ${backup_file%/*}
+if [[ -z $(pwd | grep "${SNAP_COMMON}") ]]
+then
+    echo "[-] Backup file must be within ${SNAP_COMMON}."
+    exit
+fi
+
+function ask_backup {
+    echo -n "\
+*** ATTENTION ***
+* Your current database WILL BE DROPPED prior to the restore!
+* Would you like to make a backup of the current database before proceeding?
+* (y/n/Q)> "
+
+    read choice
+    [[ "${choice,,}" = n* ]] && return
+    [[ "${choice,,}" = y* ]] && backupdb && return
+    exit
+}
+
+function warn {
+        echo "[!] ${1}"
+        echo "[*] Check ${restore_dir}/${log_name} for details."
+}
+
+function abort {
+        echo "[!] ${1}"
+        echo "[*] Check ${restore_dir}/${log_name} for details."
+        echo "[-] Restore aborted!"
+        exit
+}
+
+mongo parties --eval "db.getCollectionNames()" | grep "\[ \]" >> /dev/null || ask_backup
+echo "[*] Extracting backup file..."
+restore_dir="${SNAP_COMMON}/restore"
+log_name="extraction.log"
+mkdir -p ${restore_dir}
+cd ${restore_dir}
+tar --no-same-owner --overwrite -zxvf ${backup_file} &> "${restore_dir}/${log_name}"
+[[ $? != 0 ]] && abort "Failed to extract backup files to ${restore_dir}!"
+echo "[*] Restoring data..."
+data_dir=$(tail "${restore_dir}/${log_name}" | grep parties/. | head -n 1)
+[[ -z ${data_dir} ]] && abort "Restore data not found within ${backup_file}!
+    Please check that your backup file contains the backup data within the \"parties\" directory."
+data_dir=$(dirname ${data_dir})
+log_name="mongorestore.log"
+mongorestore --db parties --noIndexRestore --drop ${data_dir} &> "${restore_dir}/${log_name}"
+[[ $? != 0 ]] && abort "Failed to execute mongorestore from ${data_dir}!"
+# If mongorestore.log only has a few lines, it likely didn't find the dump files
+log_lines=$(wc -l < "${restore_dir}/${log_name}")
+[[ ${log_lines} -lt 24 ]] && warn "Little or no restore data found within ${backup_file}!
+    Please check that your backup file contains all the backup data within the \"parties\" directory."
+echo "[*] Preparing database..."
+log_name="mongoprepare.log"
+mongo parties --eval "db.repairDatabase()" --verbose &> "${restore_dir}/${log_name}"
+[[ $? != 0 ]] && abort "Failed to prepare database for usage!"
+echo "[+] Restore completed! Please restart the snap.rocketchat services to verify."
diff --git a/.snapcraft/stable/snapcraft.yaml b/.snapcraft/stable/snapcraft.yaml
index 9fca9ec62b6fc481bf9e1ab24f5184e2088e227f..e548ef967a894277a1659c51581942ebee2599d3 100644
--- a/.snapcraft/stable/snapcraft.yaml
+++ b/.snapcraft/stable/snapcraft.yaml
@@ -11,6 +11,7 @@ version: #{RC_VERSION}
 summary: Rocket.Chat server
 description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/
 confinement: strict
+assumes: [snapd2.21]
 apps:
     rocketchat-server:
         command: startRocketChat
@@ -24,6 +25,12 @@ apps:
         command: env LC_ALL=C caddy -conf=$SNAP_DATA/Caddyfile -host=localhost:8080
         daemon: simple
         plugs: [network, network-bind]
+    mongo:
+        command: env LC_ALL=C mongo
+        plugs: [network]
+    restoredb:
+        command: env LC_ALL=C restoredb
+        plugs: [network]
     backupdb:
         command: env LC_ALL=c rcbackup
         plugs: [network]
@@ -48,7 +55,7 @@ parts:
             lib/node_modules: node_modules
     rocketchat-server:
         plugin: dump
-        after: [mongodb]
+        after: [node]
         source: https://rocket.chat/releases/latest/download
         source-type: tar
         stage-packages:
@@ -59,22 +66,15 @@ parts:
             - .node_version.txt
             - usr
             - lib
-        snap:
-            - programs
-            - main.js
-            - .node_version.txt
-            - usr
-            - lib
     mongodb:
-        source: https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-3.2.7.tgz
+        build-packages:
+            - wget
+        source: ./
+        prepare: ./resources/preparemongo
         plugin: dump
         stage-packages:
             - libssl1.0.0
-        stage:
-            - usr
-            - bin
-            - lib
-        snap:
+        prime:
             - usr
             - bin
             - lib
@@ -83,23 +83,20 @@ parts:
         source: resources/
         organize:
             rcbackup: bin/rcbackup
+            restoredb: bin/restoredb
             startmongo: bin/startmongo
             startRocketChat: bin/startRocketChat
             initreplset.js: bin/initreplset.js
             Caddyfile: bin/Caddyfile
             initcaddy: bin/initcaddy
-        snap:
+        prime:
             - bin
     caddy:
-        plugin: go
-        go-importpath:  github.com/mholt/caddy
-        source: https://github.com/mholt/caddy
-        source-type: git
-        source-commit: 53e117802fedd5915eeb32907873d8786a4b2936
-        snap:
-            - bin/caddy
-        after: [go]
-    go:
-        source-tag: go1.8
-        stage:
-        - bin
+        prepare: ./resources/preparecaddy
+        plugin: dump
+        source: ./
+        prime:
+            - bin
+        organize:
+            caddy: bin/caddy
+        after: [mongodb]
diff --git a/.travis.yml b/.travis.yml
index 09bfc03484ba452e68017397e6886774bbf969d8..3192c2d4b13915c5050fc7386285e1f94fbc226c 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
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/client/methods/deleteMessage.js b/client/methods/deleteMessage.js
index e7acb1c30aa31b643ce644aaf8114c5d9409c819..8a86f3affc253152d5e2592b9560dcc17e651bf3 100644
--- a/client/methods/deleteMessage.js
+++ b/client/methods/deleteMessage.js
@@ -11,18 +11,18 @@ Meteor.methods({
 		message = ChatMessage.findOne({ _id: message._id });
 
 		const hasPermission = RocketChat.authz.hasAtLeastOnePermission('delete-message', message.rid);
+		const forceDelete = RocketChat.authz.hasAtLeastOnePermission('force-delete-message', message.rid);
 		const deleteAllowed = RocketChat.settings.get('Message_AllowDeleting');
 		let deleteOwn = false;
+
 		if (message && message.u && message.u._id) {
 			deleteOwn = message.u._id === Meteor.userId();
 		}
-
-		if (!(hasPermission || (deleteAllowed && deleteOwn))) {
+		if (!(forceDelete || hasPermission || (deleteAllowed && deleteOwn))) {
 			return false;
 		}
-
 		const blockDeleteInMinutes = RocketChat.settings.get('Message_AllowDeleting_BlockDeleteInMinutes');
-		if (_.isNumber(blockDeleteInMinutes) && blockDeleteInMinutes !== 0) {
+		if (!(forceDelete) || (_.isNumber(blockDeleteInMinutes) && blockDeleteInMinutes !== 0)) {
 			if (message.ts) {
 				const msgTs = moment(message.ts);
 				if (msgTs) {
diff --git a/package.json b/package.json
index 5238246908b99327d7547d466f0630ceb1165749..ec5c5b29e0b57a955382206d83e65096011af2b2 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/"
@@ -44,7 +44,8 @@
 		"chimp-test": "chimp tests/chimp-config.js",
 		"postinstall": "cd packages/rocketchat-katex && npm i",
 		"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 && conventional-changelog --config .github/changelog.js -i HISTORY.md -s"
 	},
 	"license": "MIT",
 	"repository": {
@@ -66,7 +67,7 @@
 		"babel-runtime": "^6.23.0",
 		"bcrypt": "^1.0.2",
 		"codemirror": "^5.25.2",
-		"file-type": "^4.2.0",
+		"file-type": "^4.3.0",
 		"highlight.js": "^9.11.0",
 		"jquery": "^3.2.1",
 		"mime-db": "^1.27.0",
@@ -74,7 +75,7 @@
 		"moment": "^2.18.1",
 		"moment-timezone": "^0.5.13",
 		"photoswipe": "^4.1.2",
-		"prom-client": "^8.1.1",
+		"prom-client": "^9.0.0",
 		"semver": "^5.3.0",
 		"toastr": "^2.1.2"
 	}
diff --git a/packages/rocketchat-api/package.js b/packages/rocketchat-api/package.js
index 6918b5f1da8fed725f32153405e772b459cf491d..ef8aaf159327b7b036b6c590911440315fc6646e 100644
--- a/packages/rocketchat-api/package.js
+++ b/packages/rocketchat-api/package.js
@@ -19,6 +19,7 @@ Package.onUse(function(api) {
 	//Register v1 helpers
 	api.addFiles('server/v1/helpers/getPaginationItems.js', 'server');
 	api.addFiles('server/v1/helpers/getUserFromParams.js', 'server');
+	api.addFiles('server/v1/helpers/isUserFromParams.js', 'server');
 	api.addFiles('server/v1/helpers/parseJsonQuery.js', 'server');
 	api.addFiles('server/v1/helpers/getLoggedInUser.js', 'server');
 
diff --git a/packages/rocketchat-api/server/v1/channels.js b/packages/rocketchat-api/server/v1/channels.js
index e145a84f0e1499cafd3da47ffaf589e867d87e61..1de2d82d724b238b812fdbb812f855044ddf9530 100644
--- a/packages/rocketchat-api/server/v1/channels.js
+++ b/packages/rocketchat-api/server/v1/channels.js
@@ -1,10 +1,15 @@
 //Returns the channel IF found otherwise it will return the failure of why it didn't. Check the `statusCode` property
-function findChannelById({ roomId, checkedArchived = true }) {
-	if (!roomId || !roomId.trim()) {
-		throw new Meteor.Error('error-roomid-param-not-provided', 'The parameter "roomId" is required');
+function findChannelByIdOrName({ roomId, roomName, checkedArchived = true }) {
+	if ((!roomId || !roomId.trim()) && (!roomName || !roomName.trim())) {
+		throw new Meteor.Error('error-roomid-param-not-provided', 'The parameter "roomId" or "roomName" is required');
 	}
 
-	const room = RocketChat.models.Rooms.findOneById(roomId, { fields: RocketChat.API.v1.defaultFieldsToExclude });
+	let room;
+	if (roomId) {
+		room = RocketChat.models.Rooms.findOneById(roomId, { fields: RocketChat.API.v1.defaultFieldsToExclude });
+	} else if (roomName) {
+		room = RocketChat.models.Rooms.findOneByName(roomName, { fields: RocketChat.API.v1.defaultFieldsToExclude });
+	}
 
 	if (!room || room.t !== 'c') {
 		throw new Meteor.Error('error-room-not-found', `No channel found by the id of: ${ roomId }`);
@@ -19,7 +24,7 @@ function findChannelById({ roomId, checkedArchived = true }) {
 
 RocketChat.API.v1.addRoute('channels.addAll', { authRequired: true }, {
 	post() {
-		const findResult = findChannelById({ roomId: this.bodyParams.roomId });
+		const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId });
 
 		Meteor.runAsUser(this.userId, () => {
 			Meteor.call('addAllUserToRoom', findResult._id, this.bodyParams.activeUsersOnly);
@@ -33,7 +38,7 @@ RocketChat.API.v1.addRoute('channels.addAll', { authRequired: true }, {
 
 RocketChat.API.v1.addRoute('channels.addModerator', { authRequired: true }, {
 	post() {
-		const findResult = findChannelById({ roomId: this.bodyParams.roomId });
+		const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId });
 
 		const user = this.getUserFromParams();
 
@@ -47,7 +52,7 @@ RocketChat.API.v1.addRoute('channels.addModerator', { authRequired: true }, {
 
 RocketChat.API.v1.addRoute('channels.addOwner', { authRequired: true }, {
 	post() {
-		const findResult = findChannelById({ roomId: this.bodyParams.roomId });
+		const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId });
 
 		const user = this.getUserFromParams();
 
@@ -61,7 +66,7 @@ RocketChat.API.v1.addRoute('channels.addOwner', { authRequired: true }, {
 
 RocketChat.API.v1.addRoute('channels.archive', { authRequired: true }, {
 	post() {
-		const findResult = findChannelById({ roomId: this.bodyParams.roomId });
+		const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId });
 
 		Meteor.runAsUser(this.userId, () => {
 			Meteor.call('archiveRoom', findResult._id);
@@ -73,7 +78,7 @@ RocketChat.API.v1.addRoute('channels.archive', { authRequired: true }, {
 
 RocketChat.API.v1.addRoute('channels.cleanHistory', { authRequired: true }, {
 	post() {
-		const findResult = findChannelById({ roomId: this.bodyParams.roomId });
+		const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId });
 
 		if (!this.bodyParams.latest) {
 			return RocketChat.API.v1.failure('Body parameter "latest" is required.');
@@ -101,7 +106,7 @@ RocketChat.API.v1.addRoute('channels.cleanHistory', { authRequired: true }, {
 
 RocketChat.API.v1.addRoute('channels.close', { authRequired: true }, {
 	post() {
-		const findResult = findChannelById({ roomId: this.bodyParams.roomId, checkedArchived: false });
+		const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId, checkedArchived: false });
 
 		const sub = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(findResult._id, this.userId);
 
@@ -157,7 +162,7 @@ RocketChat.API.v1.addRoute('channels.create', { authRequired: true }, {
 
 RocketChat.API.v1.addRoute('channels.delete', { authRequired: true }, {
 	post() {
-		const findResult = findChannelById({ roomId: this.bodyParams.roomId, checkedArchived: false });
+		const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId, checkedArchived: false });
 
 		//The find method returns either with the group or the failur
 
@@ -177,7 +182,7 @@ RocketChat.API.v1.addRoute('channels.getIntegrations', { authRequired: true }, {
 			return RocketChat.API.v1.unauthorized();
 		}
 
-		const findResult = findChannelById({ roomId: this.queryParams.roomId, checkedArchived: false });
+		const findResult = findChannelByIdOrName({ roomId: this.queryParams.roomId, checkedArchived: false });
 
 		let includeAllPublicChannels = true;
 		if (typeof this.queryParams.includeAllPublicChannels !== 'undefined') {
@@ -217,7 +222,7 @@ RocketChat.API.v1.addRoute('channels.getIntegrations', { authRequired: true }, {
 
 RocketChat.API.v1.addRoute('channels.history', { authRequired: true }, {
 	get() {
-		const findResult = findChannelById({ roomId: this.queryParams.roomId, checkedArchived: false });
+		const findResult = findChannelByIdOrName({ roomId: this.queryParams.roomId, checkedArchived: false });
 
 		let latestDate = new Date();
 		if (this.queryParams.latest) {
@@ -257,7 +262,7 @@ RocketChat.API.v1.addRoute('channels.history', { authRequired: true }, {
 
 RocketChat.API.v1.addRoute('channels.info', { authRequired: true }, {
 	get() {
-		const findResult = findChannelById({ roomId: this.queryParams.roomId, checkedArchived: false });
+		const findResult = findChannelByIdOrName({ roomId: this.queryParams.roomId, roomName: this.queryParams.roomName, checkedArchived: false });
 
 		return RocketChat.API.v1.success({
 			channel: RocketChat.models.Rooms.findOneById(findResult._id, { fields: RocketChat.API.v1.defaultFieldsToExclude })
@@ -267,7 +272,7 @@ RocketChat.API.v1.addRoute('channels.info', { authRequired: true }, {
 
 RocketChat.API.v1.addRoute('channels.invite', { authRequired: true }, {
 	post() {
-		const findResult = findChannelById({ roomId: this.bodyParams.roomId });
+		const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId });
 
 		const user = this.getUserFromParams();
 
@@ -283,7 +288,7 @@ RocketChat.API.v1.addRoute('channels.invite', { authRequired: true }, {
 
 RocketChat.API.v1.addRoute('channels.join', { authRequired: true }, {
 	post() {
-		const findResult = findChannelById({ roomId: this.bodyParams.roomId });
+		const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId });
 
 		Meteor.runAsUser(this.userId, () => {
 			Meteor.call('joinRoom', findResult._id, this.bodyParams.joinCode);
@@ -297,7 +302,7 @@ RocketChat.API.v1.addRoute('channels.join', { authRequired: true }, {
 
 RocketChat.API.v1.addRoute('channels.kick', { authRequired: true }, {
 	post() {
-		const findResult = findChannelById({ roomId: this.bodyParams.roomId });
+		const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId });
 
 		const user = this.getUserFromParams();
 
@@ -313,7 +318,7 @@ RocketChat.API.v1.addRoute('channels.kick', { authRequired: true }, {
 
 RocketChat.API.v1.addRoute('channels.leave', { authRequired: true }, {
 	post() {
-		const findResult = findChannelById({ roomId: this.bodyParams.roomId });
+		const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId });
 
 		Meteor.runAsUser(this.userId, () => {
 			Meteor.call('leaveRoom', findResult._id);
@@ -409,7 +414,7 @@ RocketChat.API.v1.addRoute('channels.online', { authRequired: true }, {
 
 RocketChat.API.v1.addRoute('channels.open', { authRequired: true }, {
 	post() {
-		const findResult = findChannelById({ roomId: this.bodyParams.roomId, checkedArchived: false });
+		const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId, checkedArchived: false });
 
 		const sub = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(findResult._id, this.userId);
 
@@ -431,7 +436,7 @@ RocketChat.API.v1.addRoute('channels.open', { authRequired: true }, {
 
 RocketChat.API.v1.addRoute('channels.removeModerator', { authRequired: true }, {
 	post() {
-		const findResult = findChannelById({ roomId: this.bodyParams.roomId });
+		const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId });
 
 		const user = this.getUserFromParams();
 
@@ -445,7 +450,7 @@ RocketChat.API.v1.addRoute('channels.removeModerator', { authRequired: true }, {
 
 RocketChat.API.v1.addRoute('channels.removeOwner', { authRequired: true }, {
 	post() {
-		const findResult = findChannelById({ roomId: this.bodyParams.roomId });
+		const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId });
 
 		const user = this.getUserFromParams();
 
@@ -463,7 +468,7 @@ RocketChat.API.v1.addRoute('channels.rename', { authRequired: true }, {
 			return RocketChat.API.v1.failure('The bodyParam "name" is required');
 		}
 
-		const findResult = findChannelById({ roomId: this.bodyParams.roomId });
+		const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId });
 
 		if (findResult.name === this.bodyParams.name) {
 			return RocketChat.API.v1.failure('The channel name is the same as what it would be renamed to.');
@@ -485,7 +490,7 @@ RocketChat.API.v1.addRoute('channels.setDescription', { authRequired: true }, {
 			return RocketChat.API.v1.failure('The bodyParam "description" is required');
 		}
 
-		const findResult = findChannelById({ roomId: this.bodyParams.roomId });
+		const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId });
 
 		if (findResult.description === this.bodyParams.description) {
 			return RocketChat.API.v1.failure('The channel description is the same as what it would be changed to.');
@@ -507,7 +512,7 @@ RocketChat.API.v1.addRoute('channels.setJoinCode', { authRequired: true }, {
 			return RocketChat.API.v1.failure('The bodyParam "joinCode" is required');
 		}
 
-		const findResult = findChannelById({ roomId: this.bodyParams.roomId });
+		const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId });
 
 		Meteor.runAsUser(this.userId, () => {
 			Meteor.call('saveRoomSettings', findResult._id, 'joinCode', this.bodyParams.joinCode);
@@ -525,7 +530,7 @@ RocketChat.API.v1.addRoute('channels.setPurpose', { authRequired: true }, {
 			return RocketChat.API.v1.failure('The bodyParam "purpose" is required');
 		}
 
-		const findResult = findChannelById({ roomId: this.bodyParams.roomId });
+		const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId });
 
 		if (findResult.description === this.bodyParams.purpose) {
 			return RocketChat.API.v1.failure('The channel purpose (description) is the same as what it would be changed to.');
@@ -547,7 +552,7 @@ RocketChat.API.v1.addRoute('channels.setReadOnly', { authRequired: true }, {
 			return RocketChat.API.v1.failure('The bodyParam "readOnly" is required');
 		}
 
-		const findResult = findChannelById({ roomId: this.bodyParams.roomId });
+		const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId });
 
 		if (findResult.ro === this.bodyParams.readOnly) {
 			return RocketChat.API.v1.failure('The channel read only setting is the same as what it would be changed to.');
@@ -569,7 +574,7 @@ RocketChat.API.v1.addRoute('channels.setTopic', { authRequired: true }, {
 			return RocketChat.API.v1.failure('The bodyParam "topic" is required');
 		}
 
-		const findResult = findChannelById({ roomId: this.bodyParams.roomId });
+		const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId });
 
 		if (findResult.topic === this.bodyParams.topic) {
 			return RocketChat.API.v1.failure('The channel topic is the same as what it would be changed to.');
@@ -591,7 +596,7 @@ RocketChat.API.v1.addRoute('channels.setType', { authRequired: true }, {
 			return RocketChat.API.v1.failure('The bodyParam "type" is required');
 		}
 
-		const findResult = findChannelById({ roomId: this.bodyParams.roomId });
+		const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId });
 
 		if (findResult.t === this.bodyParams.type) {
 			return RocketChat.API.v1.failure('The channel type is the same as what it would be changed to.');
@@ -609,7 +614,7 @@ RocketChat.API.v1.addRoute('channels.setType', { authRequired: true }, {
 
 RocketChat.API.v1.addRoute('channels.unarchive', { authRequired: true }, {
 	post() {
-		const findResult = findChannelById({ roomId: this.bodyParams.roomId, checkedArchived: false });
+		const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId, checkedArchived: false });
 
 		if (!findResult.archived) {
 			return RocketChat.API.v1.failure(`The channel, ${ findResult.name }, is not archived`);
diff --git a/packages/rocketchat-api/server/v1/groups.js b/packages/rocketchat-api/server/v1/groups.js
index f05538c4d6d93ab4cf61f38587b270570dbc0733..10bef449b9c1a51e83f15a76d842dc6f46861ec2 100644
--- a/packages/rocketchat-api/server/v1/groups.js
+++ b/packages/rocketchat-api/server/v1/groups.js
@@ -1,4 +1,4 @@
-//Returns the private group subscription IF found otherwise it will reutrn the failure of why it didn't. Check the `statusCode` property
+//Returns the private group subscription IF found otherwise it will return the failure of why it didn't. Check the `statusCode` property
 function findPrivateGroupByIdOrName({ roomId, roomName, userId, checkedArchived = true }) {
 	if ((!roomId || !roomId.trim()) && (!roomName || !roomName.trim())) {
 		throw new Meteor.Error('error-roomid-param-not-provided', 'The parameter "roomId" or "roomName" is required');
diff --git a/packages/rocketchat-api/server/v1/helpers/getUserFromParams.js b/packages/rocketchat-api/server/v1/helpers/getUserFromParams.js
index 99976b38990e2ba0812b9324c93c1aa81213837b..f3e4c81950b84f8a2f1b5bbb0ff77766a94d6b3e 100644
--- a/packages/rocketchat-api/server/v1/helpers/getUserFromParams.js
+++ b/packages/rocketchat-api/server/v1/helpers/getUserFromParams.js
@@ -1,33 +1,30 @@
-//Convience method, almost need to turn it into a middleware of sorts
+//Convenience method, almost need to turn it into a middleware of sorts
 RocketChat.API.v1.helperMethods.set('getUserFromParams', function _getUserFromParams() {
 	const doesntExist = { _doesntExist: true };
 	let user;
+	let params;
 
 	switch (this.request.method) {
 		case 'POST':
 		case 'PUT':
-			if (this.bodyParams.userId && this.bodyParams.userId.trim()) {
-				user = RocketChat.models.Users.findOneById(this.bodyParams.userId) || doesntExist;
-			} else if (this.bodyParams.username && this.bodyParams.username.trim()) {
-				user = RocketChat.models.Users.findOneByUsername(this.bodyParams.username) || doesntExist;
-			} else if (this.bodyParams.user && this.bodyParams.user.trim()) {
-				user = RocketChat.models.Users.findOneByUsername(this.bodyParams.user) || doesntExist;
-			}
+			params = this.bodyParams;
 			break;
 		default:
-			if (this.queryParams.userId && this.queryParams.userId.trim()) {
-				user = RocketChat.models.Users.findOneById(this.queryParams.userId) || doesntExist;
-			} else if (this.queryParams.username && this.queryParams.username.trim()) {
-				user = RocketChat.models.Users.findOneByUsername(this.queryParams.username) || doesntExist;
-			} else if (this.queryParams.user && this.queryParams.user.trim()) {
-				user = RocketChat.models.Users.findOneByUsername(this.queryParams.user) || doesntExist;
-			}
+			params = this.queryParams;
 			break;
 	}
 
-	if (!user) {
+	if (params.userId && params.userId.trim()) {
+		user = RocketChat.models.Users.findOneById(params.userId) || doesntExist;
+	} else if (params.username && params.username.trim()) {
+		user = RocketChat.models.Users.findOneByUsername(params.username) || doesntExist;
+	} else if (params.user && params.user.trim()) {
+		user = RocketChat.models.Users.findOneByUsername(params.user) || doesntExist;
+	} else {
 		throw new Meteor.Error('error-user-param-not-provided', 'The required "userId" or "username" param was not provided');
-	} else if (user._doesntExist) {
+	}
+
+	if (user._doesntExist) {
 		throw new Meteor.Error('error-invalid-user', 'The required "userId" or "username" param provided does not match any users');
 	}
 
diff --git a/packages/rocketchat-api/server/v1/helpers/isUserFromParams.js b/packages/rocketchat-api/server/v1/helpers/isUserFromParams.js
new file mode 100644
index 0000000000000000000000000000000000000000..28c8bf4ee5e8c62df0384d17c52940c56f5ddb9e
--- /dev/null
+++ b/packages/rocketchat-api/server/v1/helpers/isUserFromParams.js
@@ -0,0 +1,18 @@
+RocketChat.API.v1.helperMethods.set('isUserFromParams', function _isUserFromParams() {
+	let params;
+
+	switch (this.request.method) {
+		case 'POST':
+		case 'PUT':
+			params = this.bodyParams;
+			break;
+		default:
+			params = this.queryParams;
+			break;
+	}
+
+	return (!params.userId && !params.username && !params.user) ||
+		(params.userId && this.userId === params.userId) ||
+		(params.username && this.user.username === params.username) ||
+		(params.user && this.user.username === params.user);
+});
diff --git a/packages/rocketchat-api/server/v1/users.js b/packages/rocketchat-api/server/v1/users.js
index e808a0ff10b6915eca4c53c2636e1456b388b39b..aa66fab610f0714dc65cd1eb7aaa007a5b23b360 100644
--- a/packages/rocketchat-api/server/v1/users.js
+++ b/packages/rocketchat-api/server/v1/users.js
@@ -67,20 +67,19 @@ RocketChat.API.v1.addRoute('users.getAvatar', { authRequired: false }, {
 
 RocketChat.API.v1.addRoute('users.getPresence', { authRequired: true }, {
 	get() {
-		//BLAHHHHHHHHHH :'(
-		if ((this.queryParams.userId && this.userId !== this.queryParams.userId) || (this.queryParams.username && this.user.username !== this.queryParams.username) || (this.queryParams.user && this.user.username !== this.queryParams.user)) {
-			const user = this.getUserFromParams();
-
+		if (this.isUserFromParams()) {
+			const user = RocketChat.models.Users.findOneById(this.userId);
 			return RocketChat.API.v1.success({
-				presence: user.status
+				presence: user.status,
+				connectionStatus: user.statusConnection,
+				lastLogin: user.lastLogin
 			});
 		}
 
-		const user = RocketChat.models.Users.findOneById(this.userId);
+		const user = this.getUserFromParams();
+
 		return RocketChat.API.v1.success({
-			presence: user.status,
-			connectionStatus: user.statusConnection,
-			lastLogin: user.lastLogin
+			presence: user.status
 		});
 	}
 });
@@ -185,17 +184,19 @@ RocketChat.API.v1.addRoute('users.resetAvatar', { authRequired: true }, {
 	}
 });
 
-//TODO: Make this route work with support for usernames
 RocketChat.API.v1.addRoute('users.setAvatar', { authRequired: true }, {
 	post() {
 		check(this.bodyParams, { avatarUrl: Match.Maybe(String), userId: Match.Maybe(String) });
 
-		if (typeof this.bodyParams.userId !== 'undefined' && this.userId !== this.bodyParams.userId && !RocketChat.authz.hasPermission(this.userId, 'edit-other-user-info')) {
+		let user;
+		if (this.isUserFromParams()) {
+			user = Meteor.users.findOne(this.userId);
+		} else if (RocketChat.authz.hasPermission(this.userId, 'edit-other-user-info')) {
+			user = this.getUserFromParams();
+		} else {
 			return RocketChat.API.v1.unauthorized();
 		}
 
-		const user = Meteor.users.findOne(this.bodyParams.userId ? this.bodyParams.userId : this.userId);
-
 		if (this.bodyParams.avatarUrl) {
 			RocketChat.setUserAvatar(user, this.bodyParams.avatarUrl, '', 'url');
 		} else {
@@ -248,7 +249,7 @@ RocketChat.API.v1.addRoute('users.update', { authRequired: true }, {
 
 		const userData = _.extend({ _id: this.bodyParams.userId }, this.bodyParams.data);
 
-		RocketChat.saveUser(this.userId, userData);
+		Meteor.runAsUser(this.userId, () => RocketChat.saveUser(this.userId, userData));
 
 		if (this.bodyParams.data.customFields) {
 			RocketChat.saveCustomFields(this.bodyParams.userId, this.bodyParams.data.customFields);
@@ -263,3 +264,14 @@ RocketChat.API.v1.addRoute('users.update', { authRequired: true }, {
 		return RocketChat.API.v1.success({ user: RocketChat.models.Users.findOneById(this.bodyParams.userId, { fields: RocketChat.API.v1.defaultFieldsToExclude }) });
 	}
 });
+
+RocketChat.API.v1.addRoute('users.createToken', { authRequired: true }, {
+	post() {
+		const user = this.getUserFromParams();
+		let data;
+		Meteor.runAsUser(this.userId, () => {
+			data = Meteor.call('createToken', user._id);
+		});
+		return data ? RocketChat.API.v1.success({data}) : RocketChat.API.v1.unauthorized();
+	}
+});
diff --git a/packages/rocketchat-authorization/server/functions/canAccessRoom.js b/packages/rocketchat-authorization/server/functions/canAccessRoom.js
index faf4164698f58ea4dc6a54315244ea84f3656f74..82a6b761c1bbaf9dee841f0f28946da859a5a37f 100644
--- a/packages/rocketchat-authorization/server/functions/canAccessRoom.js
+++ b/packages/rocketchat-authorization/server/functions/canAccessRoom.js
@@ -2,7 +2,7 @@
 RocketChat.authz.roomAccessValidators = [
 	function(room, user = {}) {
 		if (room.t === 'c') {
-			if (!user._id && RocketChat.settings.get('Accounts_AllowAnonymousAccess') === true) {
+			if (!user._id && RocketChat.settings.get('Accounts_AllowAnonymousRead') === true) {
 				return true;
 			}
 
diff --git a/packages/rocketchat-authorization/server/startup.js b/packages/rocketchat-authorization/server/startup.js
index cf4ac5c7bd7224f2b9c91d5ab924212a3b86ce32..3fe7771125de4edc9d9be81a81f2c6d5111b1316 100644
--- a/packages/rocketchat-authorization/server/startup.js
+++ b/packages/rocketchat-authorization/server/startup.js
@@ -32,6 +32,7 @@ Meteor.startup(function() {
 		{ _id: 'edit-other-user-password',      roles : ['admin'] },
 		{ _id: 'edit-privileged-setting',       roles : ['admin'] },
 		{ _id: 'edit-room',                     roles : ['admin', 'owner', 'moderator'] },
+		{ _id: 'force-delete-message',          roles : ['admin', 'owner'] },
 		{ _id: 'join-without-join-code',        roles : ['admin', 'bot'] },
 		{ _id: 'manage-assets',                 roles : ['admin'] },
 		{ _id: 'manage-emoji',                  roles : ['admin'] },
@@ -46,20 +47,21 @@ Meteor.startup(function() {
 		{ _id: 'set-moderator',                 roles : ['admin', 'owner'] },
 		{ _id: 'set-owner',                     roles : ['admin', 'owner'] },
 		{ _id: 'unarchive-room',                roles : ['admin'] },
-		{ _id: 'view-c-room',                   roles : ['admin', 'user', 'bot'] },
+		{ _id: 'view-c-room',                   roles : ['admin', 'user', 'bot', 'anonymous'] },
+		{ _id: 'user-generate-access-token',    roles : ['admin'] },
 		{ _id: 'view-d-room',                   roles : ['admin', 'user', 'bot'] },
 		{ _id: 'view-full-other-user-info',     roles : ['admin'] },
-		{ _id: 'view-history',                  roles : ['admin', 'user'] },
-		{ _id: 'view-joined-room',              roles : ['guest', 'bot'] },
+		{ _id: 'view-history',                  roles : ['admin', 'user', 'anonymous'] },
+		{ _id: 'view-joined-room',              roles : ['guest', 'bot', 'anonymous'] },
 		{ _id: 'view-join-code',                roles : ['admin'] },
 		{ _id: 'view-logs',                     roles : ['admin'] },
 		{ _id: 'view-other-user-channels',      roles : ['admin'] },
-		{ _id: 'view-p-room',                   roles : ['admin', 'user'] },
+		{ _id: 'view-p-room',                   roles : ['admin', 'user', 'anonymous'] },
 		{ _id: 'view-privileged-setting',       roles : ['admin'] },
 		{ _id: 'view-room-administration',      roles : ['admin'] },
 		{ _id: 'view-statistics',               roles : ['admin'] },
 		{ _id: 'view-user-administration',      roles : ['admin'] },
-		{ _id: 'preview-c-room',                roles : ['admin', 'user'] }
+		{ _id: 'preview-c-room',                roles : ['admin', 'user', 'anonymous'] }
 	];
 
 	for (const permission of permissions) {
@@ -74,7 +76,8 @@ Meteor.startup(function() {
 		{ name: 'owner',     scope: 'Subscriptions', description: 'Owner' },
 		{ name: 'user',      scope: 'Users',         description: '' },
 		{ name: 'bot',       scope: 'Users',         description: '' },
-		{ name: 'guest',     scope: 'Users',         description: '' }
+		{ name: 'guest',     scope: 'Users',         description: '' },
+		{ name: 'anonymous', scope: 'Users',         description: '' }
 	];
 
 	for (const role of defaultRoles) {
diff --git a/packages/rocketchat-channel-settings/client/lib/ChannelSettings.js b/packages/rocketchat-channel-settings/client/lib/ChannelSettings.js
index 947c32b10ac997f171e40f693ccf048291688f6d..1b222ab393569b3ab6ad8e491176ea9e4e63468e 100644
--- a/packages/rocketchat-channel-settings/client/lib/ChannelSettings.js
+++ b/packages/rocketchat-channel-settings/client/lib/ChannelSettings.js
@@ -25,7 +25,7 @@ RocketChat.ChannelSettings = new class {
 		const allOptions = _.toArray(this.options.get());
 		const allowedOptions = _.compact(_.map(allOptions, function(option) {
 			if (option.validation == null || option.validation()) {
-				option.data = Object.assign({}, option.data, currentData);
+				option.data = Object.assign({}, typeof option.data === 'function' ? option.data() : option.data, currentData);
 				return option;
 			}
 		})).filter(function(option) {
diff --git a/packages/rocketchat-file-upload/lib/FileUploadBase.js b/packages/rocketchat-file-upload/lib/FileUploadBase.js
index 62263dd0c6d9b8f081726b213ca49611dcabf8eb..24a304cf28882e196c234e71c3a7aaddd236de2f 100644
--- a/packages/rocketchat-file-upload/lib/FileUploadBase.js
+++ b/packages/rocketchat-file-upload/lib/FileUploadBase.js
@@ -2,8 +2,8 @@
 /* exported FileUploadBase */
 
 UploadFS.config.defaultStorePermissions = new UploadFS.StorePermissions({
-	insert(userId/*, doc*/) {
-		return userId;
+	insert(userId, doc) {
+		return userId || (doc && doc.message_id && doc.message_id.indexOf('slack-') === 0); // allow inserts from slackbridge (message_id = slack-timestamp-milli)
 	},
 	update(userId, doc) {
 		return RocketChat.authz.hasPermission(Meteor.userId(), 'delete-message', doc.rid) || (RocketChat.settings.get('Message_AllowDeleting') && userId === doc.userId);
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 e89b775dfaf7e205e5ecd26562d14f10bbcb3900..e171621ad42f49f5aeb214b231cd0a9f40a36a58 100644
--- a/packages/rocketchat-google-natural-language/.npm/package/npm-shrinkwrap.json
+++ b/packages/rocketchat-google-natural-language/.npm/package/npm-shrinkwrap.json
@@ -1,8 +1,8 @@
 {
   "dependencies": {
     "ajv": {
-      "version": "4.11.7",
-      "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.7.tgz",
+      "version": "4.11.8",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz",
       "from": "ajv@>=4.9.1 <5.0.0"
     },
     "ansi-regex": {
@@ -46,8 +46,8 @@
       "from": "assert-plus@>=0.2.0 <0.3.0"
     },
     "async": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/async/-/async-2.3.0.tgz",
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/async/-/async-2.4.0.tgz",
       "from": "async@>=2.1.2 <3.0.0"
     },
     "asynckit": {
@@ -88,7 +88,7 @@
     "brace-expansion": {
       "version": "1.1.7",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz",
-      "from": "brace-expansion@>=1.0.0 <2.0.0"
+      "from": "brace-expansion@>=1.1.7 <2.0.0"
     },
     "buffer-equal-constant-time": {
       "version": "1.0.1",
@@ -233,8 +233,8 @@
       "from": "escape-string-regexp@>=1.0.2 <2.0.0"
     },
     "extend": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz",
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
       "from": "extend@>=3.0.0 <4.0.0"
     },
     "extsprintf": {
@@ -268,8 +268,8 @@
       "from": "generate-object-property@>=1.1.0 <2.0.0"
     },
     "getpass": {
-      "version": "0.1.6",
-      "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz",
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
       "from": "getpass@>=0.1.1 <0.2.0",
       "dependencies": {
         "assert-plus": {
@@ -322,8 +322,8 @@
       "from": "graceful-readlink@>=1.0.0"
     },
     "grpc": {
-      "version": "1.2.4",
-      "resolved": "https://registry.npmjs.org/grpc/-/grpc-1.2.4.tgz",
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/grpc/-/grpc-1.3.1.tgz",
       "from": "grpc@>=1.1.0 <2.0.0",
       "dependencies": {
         "node-pre-gyp": {
@@ -378,8 +378,8 @@
               "from": "npmlog@>=4.0.2 <5.0.0",
               "dependencies": {
                 "are-we-there-yet": {
-                  "version": "1.1.2",
-                  "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz",
+                  "version": "1.1.4",
+                  "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz",
                   "from": "are-we-there-yet@>=1.1.2 <1.2.0",
                   "dependencies": {
                     "delegates": {
@@ -390,7 +390,7 @@
                     "readable-stream": {
                       "version": "2.2.9",
                       "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz",
-                      "from": "readable-stream@>=2.0.0 <3.0.0||>=1.1.13 <2.0.0",
+                      "from": "readable-stream@>=2.0.6 <3.0.0",
                       "dependencies": {
                         "buffer-shims": {
                           "version": "1.0.0",
@@ -437,8 +437,8 @@
                   "from": "console-control-strings@>=1.1.0 <1.2.0"
                 },
                 "gauge": {
-                  "version": "2.7.3",
-                  "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.3.tgz",
+                  "version": "2.7.4",
+                  "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
                   "from": "gauge@>=2.7.1 <2.8.0",
                   "dependencies": {
                     "aproba": {
@@ -571,8 +571,8 @@
                   }
                 },
                 "extend": {
-                  "version": "3.0.0",
-                  "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz",
+                  "version": "3.0.1",
+                  "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
                   "from": "extend@>=3.0.0 <3.1.0"
                 },
                 "forever-agent": {
@@ -598,8 +598,8 @@
                   "from": "har-validator@>=4.2.1 <4.3.0",
                   "dependencies": {
                     "ajv": {
-                      "version": "4.11.6",
-                      "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.6.tgz",
+                      "version": "4.11.8",
+                      "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz",
                       "from": "ajv@>=4.9.1 <5.0.0",
                       "dependencies": {
                         "co": {
@@ -693,8 +693,8 @@
                       }
                     },
                     "sshpk": {
-                      "version": "1.11.0",
-                      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.11.0.tgz",
+                      "version": "1.13.0",
+                      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.0.tgz",
                       "from": "sshpk@>=1.7.0 <2.0.0",
                       "dependencies": {
                         "asn1": {
@@ -723,8 +723,8 @@
                           "from": "ecc-jsbn@>=0.1.1 <0.2.0"
                         },
                         "getpass": {
-                          "version": "0.1.6",
-                          "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz",
+                          "version": "0.1.7",
+                          "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
                           "from": "getpass@>=0.1.1 <0.2.0"
                         },
                         "jodid25519": {
@@ -939,14 +939,14 @@
               "from": "tar-pack@>=3.4.0 <4.0.0",
               "dependencies": {
                 "debug": {
-                  "version": "2.6.3",
-                  "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.3.tgz",
+                  "version": "2.6.6",
+                  "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.6.tgz",
                   "from": "debug@>=2.2.0 <3.0.0",
                   "dependencies": {
                     "ms": {
-                      "version": "0.7.2",
-                      "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz",
-                      "from": "ms@0.7.2"
+                      "version": "0.7.3",
+                      "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.3.tgz",
+                      "from": "ms@0.7.3"
                     }
                   }
                 },
@@ -963,7 +963,7 @@
                     "inherits": {
                       "version": "2.0.3",
                       "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
-                      "from": "inherits@>=2.0.0 <2.1.0"
+                      "from": "inherits@>=2.0.1 <2.1.0"
                     }
                   }
                 },
@@ -1033,7 +1033,7 @@
                     "inherits": {
                       "version": "2.0.3",
                       "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
-                      "from": "inherits@>=2.0.0 <2.1.0"
+                      "from": "inherits@>=2.0.1 <2.1.0"
                     },
                     "isarray": {
                       "version": "1.0.0",
@@ -1139,8 +1139,8 @@
       "from": "is-property@>=1.0.0 <2.0.0"
     },
     "is-stream-ended": {
-      "version": "0.1.0",
-      "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.0.tgz",
+      "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": {
@@ -1261,8 +1261,8 @@
       "from": "mime-types@>=2.1.7 <2.2.0"
     },
     "minimatch": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz",
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
       "from": "minimatch@>=3.0.2 <4.0.0"
     },
     "modelo": {
@@ -1413,16 +1413,9 @@
       "from": "sntp@>=1.0.0 <2.0.0"
     },
     "split-array-stream": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/split-array-stream/-/split-array-stream-1.0.0.tgz",
-      "from": "split-array-stream@>=1.0.0 <2.0.0",
-      "dependencies": {
-        "async": {
-          "version": "1.5.2",
-          "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
-          "from": "async@>=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",
@@ -1437,8 +1430,8 @@
       }
     },
     "stream-events": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.1.tgz",
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.2.tgz",
       "from": "stream-events@>=1.0.1 <2.0.0"
     },
     "stream-shift": {
@@ -1472,9 +1465,9 @@
       "from": "strip-ansi@>=3.0.0 <4.0.0"
     },
     "stubs": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/stubs/-/stubs-1.1.2.tgz",
-      "from": "stubs@>=1.1.0 <2.0.0"
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz",
+      "from": "stubs@>=3.0.0 <4.0.0"
     },
     "supports-color": {
       "version": "2.0.0",
diff --git a/packages/rocketchat-i18n/i18n/bg.i18n.json b/packages/rocketchat-i18n/i18n/bg.i18n.json
index fbedfc1e680bcccd56ea91e6c9ead847b979af6a..893252288ea96917be8e73ad5559b94db11f58ec 100644
--- a/packages/rocketchat-i18n/i18n/bg.i18n.json
+++ b/packages/rocketchat-i18n/i18n/bg.i18n.json
@@ -1,6 +1,8 @@
 {
   "#channel": "#канал",
   "0_Errors_Only": "0 - Само Грешки",
+  "1_Errors_and_Information": "1 - Грешки и Информация",
+  "2_Erros_Information_and_Debug": "2 - Грешки, Информация и Дебъг",
   "403": "Забранен",
   "@username": "@потребителско име",
   "@username_message": "@потребителско име<message>",
@@ -23,6 +25,7 @@
   "Accounts_OAuth_Facebook": "Влизана с Facebook профил",
   "Accounts_OAuth_Google": "Влизане с Google профил",
   "Accounts_RegistrationForm_Public": "Обществен",
+  "All_channels": "Всички канали",
   "All_messages": "Всички съобщения",
   "and": "и",
   "Application_Name": "Име на Приложение",
@@ -38,6 +41,8 @@
   "Busy_male": "Зает",
   "channel": "канал",
   "Channel": "Канал",
+  "Channels": "Канали",
+  "Channels_list": "Списък на публични канали",
   "Click_here": "Натисни тук",
   "close": "затвори",
   "Close": "Затвори",
@@ -84,6 +89,7 @@
   "LDAP_Port": "Порт",
   "Leave_room": "Излез от стаята",
   "line": "линия",
+  "List_of_Channels": "Списък с Канали",
   "Loading...": "Зареждане...",
   "Log_Package": "Покажи пакет",
   "Login": "Влез",
@@ -96,6 +102,7 @@
   "Message_too_long": "Съобщението е твърде дълго",
   "Messages": "Съобщения",
   "minutes": "Минути",
+  "More_channels": "Още канали",
   "My_Account": "Моя Профил",
   "n_messages": "%s съобщения",
   "N_new_messages": "%s нови съобщения",
@@ -112,6 +119,7 @@
   "Online": "На линия",
   "Oops!": "Упс",
   "Open": "Отвори",
+  "Password": "Парола",
   "People": "Хора",
   "Please_add_a_comment": "Добави коментар моля",
   "Please_wait": "Моля изчакайте",
@@ -145,6 +153,8 @@
   "Site_Name": "Име на Сайта",
   "Skip": "Прескочи",
   "Smarsh_MissingEmail_Email": "Липсваща Електрона поща",
+  "Stats_Total_Channels": "Общо Канали",
+  "Stats_Total_Messages_Channel": "Общо Съобщения в Канали",
   "Test_Desktop_Notifications": "Изпробвай Известия на работния плот",
   "Thank_you_exclamation_mark": "Благодаря!",
   "The_server_will_restart_in_s_seconds": "Сървъра ще бъде рестартиран след %s секунди",
diff --git a/packages/rocketchat-i18n/i18n/ca.i18n.json b/packages/rocketchat-i18n/i18n/ca.i18n.json
index c80a1e4b61fba829f4297f2e533a21d0d096fbe7..436bcca1e9de01d37326825effe7f30fbfa4fe04 100644
--- a/packages/rocketchat-i18n/i18n/ca.i18n.json
+++ b/packages/rocketchat-i18n/i18n/ca.i18n.json
@@ -17,6 +17,7 @@
   "Accessing_permissions": "L'accés als permisos",
   "Account_SID": "Compte SID",
   "Accounts": "Comptes",
+  "Accounts_AllowAnonymousAccess": "Permet accés anònim",
   "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 ",
@@ -416,6 +417,7 @@
   "Desktop_Notifications_Enabled": "Les notificacions d'escriptori estan activades",
   "Direct_message_someone": "Envia un missatge directe a algú",
   "Direct_Messages": "Missatges directes",
+  "Disable_Notifications": "Desactiva notificacions",
   "Disable_two-factor_authentication": "Desactiva l'autenticació de dos factors",
   "Display_offline_form": "Mostra el formulari de fora de línia",
   "Displays_action_text": "Mostra text de l'acció",
@@ -644,6 +646,7 @@
   "Hide_room": "Oculta sala",
   "Hide_Room_Warning": "Segur que voleu ocultar la sala \"%s\"?",
   "Hide_roles": "Amaga rols",
+  "Hide_Unread_Room_Status": "Amaga l'estat de sales no llegides",
   "Hide_usernames": "Oculta els noms d'usuari",
   "Highlights": "Ressalta",
   "Highlights_How_To": "Per ser notificat quan algú esmenta una paraula o frase, afegeix-la aquí. Es poden separar les paraules o frases amb comes. No es distingeix entre majúscules i minúscules.",
@@ -1082,6 +1085,7 @@
   "Nothing_found": "No s'ha trobat res",
   "Notification_Duration": "Duració de la notificació",
   "Notifications": "Notificacions",
+  "Notifications_Muted_Description": "Si esculls silenciar-ho tot, no veuràs la sala destacada a la llista quan hi hagi nous missatges, excepte si són mencions. Silenciar les notificacions sobreescriurà les opcions de notificació.",
   "Notify_all_in_this_room": "Notifica a tothom d'aquest canal",
   "Notify_active_in_this_room": "Notifica als usuaris actius d'aquesta sala",
   "Num_Agents": "# d'agents",
@@ -1224,6 +1228,7 @@
   "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",
@@ -1547,7 +1552,7 @@
   "Unmute_user": "Dóna veu a l'usuari",
   "Unnamed": "Sense nom",
   "Unpin_Message": "Desfixa el missatge",
-  "Unread_Alert": "Alerta de no llegit",
+  "Unread_Tray_Icon_Alert": "Icona d'alerta de no llegits a la safata",
   "Unread_Messages": "Missatges no llegits",
   "Unread_Rooms": "Sales no llegides",
   "Unread_Rooms_Mode": "Mode de sales no llegides",
diff --git a/packages/rocketchat-i18n/i18n/cs.i18n.json b/packages/rocketchat-i18n/i18n/cs.i18n.json
index 29faa0d6d50cfa45b2b9d3b5a3d752b3500ba76c..ba2dab094d9aaf1329a074484b881bde67931f71 100644
--- a/packages/rocketchat-i18n/i18n/cs.i18n.json
+++ b/packages/rocketchat-i18n/i18n/cs.i18n.json
@@ -17,6 +17,7 @@
   "Accessing_permissions": "Přístup k oprávnění",
   "Account_SID": "SID účtu",
   "Accounts": "Účty",
+  "Accounts_AllowAnonymousAccess": "Povolit anonymní přístup",
   "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",
@@ -62,6 +63,10 @@
   "Accounts_OAuth_Custom_Token_Path": "Cesta k tokenu",
   "Accounts_OAuth_Custom_Token_Sent_Via": "Token odesílány přes",
   "Accounts_OAuth_Custom_Username_Field": "Pole uživatelské jméno",
+  "Accounts_OAuth_Drupal": "Povolit Drupal přihlášení",
+  "Accounts_OAuth_Drupal_callback_url": "Drupal oAuth2 URI Přesměrování",
+  "Accounts_OAuth_Drupal_id": "Drupal oAuth2 ID klienta",
+  "Accounts_OAuth_Drupal_secret": "Drupal oAuth2 Secret klienta",
   "Accounts_OAuth_Facebook": "Facebook Přihlášení",
   "Accounts_OAuth_Facebook_callback_url": "Facebook Callback URL",
   "Accounts_OAuth_Facebook_id": "Facebook App Id",
@@ -170,6 +175,8 @@
   "API_CORS_Origin": "CORS Origin",
   "API_Default_Count": "Výchozí počet",
   "API_Default_Count_Description": "Výchozí počet výsledků v REST API pokud není zažádáno konkrétní číslo",
+  "API_Drupal_URL": "Drupal URL Serveru",
+  "API_Drupal_URL_Description": "Například: https://domain.com (bez lomítka na konci)",
   "API_Embed": "Náhled vložených odkazů",
   "API_Embed_Description": "Zda zobrazit náhled stránky když uživatel pošle odkaz",
   "API_EmbedCacheExpirationDays": "Počet dní expirace cache embed",
@@ -410,6 +417,7 @@
   "Desktop_Notifications_Enabled": "Oznámení na ploše jsou povolena",
   "Direct_message_someone": "Přímá zpráva někomu",
   "Direct_Messages": "Přímé zprávy",
+  "Disable_Notifications": "Zakázat notifikace",
   "Disable_two-factor_authentication": "Zakázat dvoufázové ověření",
   "Display_offline_form": "Zobrazit offline formulář",
   "Displays_action_text": "Zobrazuje text akce",
@@ -638,6 +646,7 @@
   "Hide_room": "Skrýt místnost",
   "Hide_Room_Warning": "Jste si jisti, že chcete skrýt místnost \"%s\"?",
   "Hide_roles": "Schovat role",
+  "Hide_Unread_Room_Status": "Schovat stav nepřečtených místností",
   "Hide_usernames": "Skrýt uživatelská jména",
   "Highlights": "Klíčová slova",
   "Highlights_How_To": "Chcete-li být upozorněni, když někdo zmíní slovo nebo frázi, přidejte jej sem. Můžete oddělit slova nebo fráze čárkami. Velikost písmen nehraje roli",
@@ -730,6 +739,8 @@
   "Integration_Retry_Count_Description": "Kolikrát by se integrace měla znova pokusit volat URL pokud byl první pokus neúspěšný?",
   "Integration_Retry_Delay": "Čas prodlení opakování",
   "Integration_Retry_Delay_Description": "Jaký algoritmus prodlení by se měl použít?<code class=\"inline\">10^x</code>, <code class=\"inline\">2^x</code> nebo <code class=\"inline\">x*2</code>",
+  "Integration_Run_When_Message_Is_Edited": "Spustit při editaci",
+  "Integration_Run_When_Message_Is_Edited_Description": "Měla by být integrace spuštěna po editaci zprávy? Pokud volbu vypnete, integrace se bude spouštět pouze pro <strong>nové</strong> zprávy.",
   "Integration_Word_Trigger_Placement": "Přepisování slov kdekoliv",
   "Integration_Word_Trigger_Placement_Description": "Mělo by se vyvolat pokud je slovo umístěno jinde než na začátku?",
   "Integration_updated": "Integrace byla aktualizována",
@@ -1074,6 +1085,7 @@
   "Nothing_found": "Nic nalezeno",
   "Notification_Duration": "Délka zobrazení oznámení",
   "Notifications": "Oznámení",
+  "Notifications_Muted_Description": "Pokud ztišíte všechno, neuvidíte zvýrazněné místnosti s novými zprávami, krom zmínek. Ztišení notifikací přetěží nastavení notifikací v jednotlivých místnostech.",
   "Notify_all_in_this_room": "Oznámit všem v této místnosti",
   "Notify_active_in_this_room": "Notifikovat aktivní uživatele v místnosti",
   "Num_Agents": "# Operátorů",
@@ -1216,6 +1228,7 @@
   "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",
@@ -1539,7 +1552,7 @@
   "Unmute_user": "Zrušit ztlumení uživatele",
   "Unnamed": "Nepojmenovaný",
   "Unpin_Message": "Odepnout Zprávu",
-  "Unread_Alert": "Nepřečtené upozornění",
+  "Unread_Tray_Icon_Alert": "Ikona v oznamovací oblasti upozorňuje na nepřečtené zprávy",
   "Unread_Messages": "Nepřečtěné zprávy",
   "Unread_Rooms": "Nepřečtené místnosti",
   "Unread_Rooms_Mode": "Mód Nepřečtených místností",
diff --git a/packages/rocketchat-i18n/i18n/de-AT.i18n.json b/packages/rocketchat-i18n/i18n/de-AT.i18n.json
index d342f6c1d80602d954a3b2f17b1c1f420b9577a9..95bf6f0116731ae1a8db1a1a98390008a1ecc203 100644
--- a/packages/rocketchat-i18n/i18n/de-AT.i18n.json
+++ b/packages/rocketchat-i18n/i18n/de-AT.i18n.json
@@ -1127,7 +1127,6 @@
   "Unmute_user": "Benutzern das Chatten erlauben ",
   "Unnamed": "Unbenannt",
   "Unpin_Message": "Nachicht nicht mehr fixieren",
-  "Unread_Alert": "Ãœber Ungelesenes benachrichtigen",
   "Unread_Rooms": "Ungelesene Räume",
   "Unread_Rooms_Mode": "Ungelesene Räume aufgelistet anzeigen ",
   "Unstar_Message": "Markierung entfernen",
diff --git a/packages/rocketchat-i18n/i18n/de.i18n.json b/packages/rocketchat-i18n/i18n/de.i18n.json
index 18f8dd6d08c79bfd911c4854b679ad4f7680ae54..24e7b29e401dcc777e1c19727e05da759fc595ea 100644
--- a/packages/rocketchat-i18n/i18n/de.i18n.json
+++ b/packages/rocketchat-i18n/i18n/de.i18n.json
@@ -1072,7 +1072,7 @@
   "Selected_agents": "Ausgewählte Agenten",
   "Send": "Senden",
   "Send_a_message": "Eine Nachricht schicken",
-  "Send_a_test_mail_to_my_user": "Eine Test-E-Mail an die Benutzer senden",
+  "Send_a_test_mail_to_my_user": "Eine Test-E-Mail an mich senden",
   "Send_a_test_push_to_my_user": "Eine Test-Push-Nachricht an mich senden",
   "Send_confirmation_email": "Bestätigungsmail versenden",
   "Send_data_into_RocketChat_in_realtime": "Daten in Rocket.Chat in Echtzeit senden.",
@@ -1089,7 +1089,7 @@
   "Sending": "Senden...",
   "Service": "Service",
   "Set_as_moderator": "Zum Moderator ernennen",
-  "Set_as_owner": "zum Besitzer machen",
+  "Set_as_owner": "Zum Besitzer machen",
   "Settings": "Einstellungen",
   "Settings_updated": "Die Einstellungen wurden aktualisiert.",
   "Share_Location_Title": "Standort teilen?",
@@ -1233,7 +1233,6 @@
   "Unmute_user": "Benutzern das Chatten erlauben ",
   "Unnamed": "Unbenannt",
   "Unpin_Message": "Nachicht nicht mehr anheften",
-  "Unread_Alert": "Ãœber Ungelesenes benachrichtigen",
   "Unread_Rooms": "Ungelesene Räume",
   "Unread_Rooms_Mode": "Ungelesene Räume aufgelistet anzeigen ",
   "Unstar_Message": "Markierung entfernen",
diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json
index 3c3fef7b1187238dbf4815cfbf3398308cf1fdf8..1d18d31ef92382a2ac30d5dd485734ed7c9739f6 100644
--- a/packages/rocketchat-i18n/i18n/en.i18n.json
+++ b/packages/rocketchat-i18n/i18n/en.i18n.json
@@ -17,7 +17,8 @@
   "Accessing_permissions": "Accessing permissions",
   "Account_SID": "Account SID",
   "Accounts": "Accounts",
-  "Accounts_AllowAnonymousAccess": "Allow anonymous access",
+  "Accounts_AllowAnonymousRead": "Allow anonymous read",
+  "Accounts_AllowAnonymousWrite": "Allow anonymous write",
   "Accounts_AllowDeleteOwnAccount": "Allow users to delete own account",
   "Accounts_AllowedDomainsList": "Allowed Domains List",
   "Accounts_AllowedDomainsList_Description": "Comma-separated list of allowed domains",
@@ -35,6 +36,7 @@
   "Accounts_BlockedUsernameList": "Blocked Username List",
   "Accounts_BlockedUsernameList_Description": "Comma-separated list of blocked usernames (case-insensitive)",
   "Accounts_CustomFields_Description": "Should be a valid JSON where keys are the field names containing a dictionary of field settings. Example:<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": "Default username prefix suggestion",
   "Accounts_denyUnverifiedEmail": "Deny unverified email",
   "Accounts_EmailVerification": "Email Verification",
   "Accounts_EmailVerification_Description": "Make sure you have correct SMTP settings to use this feature",
@@ -668,11 +670,11 @@
   "Iframe_Integration_receive_enable": "Enable Receive",
   "Iframe_Integration_receive_enable_Description": "Allow parent window to send commands to Rocket.Chat.",
   "Iframe_Integration_receive_origin": "Receive origins",
-  "Iframe_Integration_receive_origin_Description": "Only pages with given origin will be able to send commands or `*` for all origins. You can use multiple values separated by `,`. Example `http://localhost,https://localhost`",
+  "Iframe_Integration_receive_origin_Description": "Origins with protocol prefix, separated by commas, which are allowed to receive commands e.g. 'https://localhost, http://localhost', or * to allow receiving from anywhere.",
   "Iframe_Integration_send_enable": "Enable Send",
   "Iframe_Integration_send_enable_Description": "Send events to parent window",
   "Iframe_Integration_send_target_origin": "Send target origin",
-  "Iframe_Integration_send_target_origin_Description": "Only pages with given origin will be able to listen to events or `*` for all origins. Example `http://localhost`",
+  "Iframe_Integration_send_target_origin_Description": "Origin with protocol prefix, which commands are sent to e.g. 'https://localhost', or * to allow sending to anywhere.",
   "Importer_Archived": "Archived",
   "Importer_CSV_Information": "The CSV importer requires a specific format, please read the documentation for how to structure your zip file:",
   "Importer_HipChatEnterprise_Information": "The file uploaded must be a decrypted tar.gz, please read the documentation for further information:",
@@ -1124,6 +1126,7 @@
   "or": "or",
   "Open_your_authentication_app_and_enter_the_code": "Open your authentication app and enter the code. You can also use one of your backup codes.",
   "Order": "Order",
+  "Or_talk_as_anonymous": "Or talk as anonymous",
   "OS_Arch": "OS Arch",
   "OS_Cpus": "OS CPU Count",
   "OS_Freemem": "OS Free Memory",
@@ -1228,7 +1231,6 @@
   "Register": "Register a new account",
   "Registration": "Registration",
   "Registration_Succeeded": "Registration Succeeded",
-  "Register_or_login_to_send_messages": "Register or login to send messages",
   "Registration_via_Admin": "Registration via Admin",
   "Regular_Expressions": "Regular Expressions",
   "Release": "Release",
@@ -1363,6 +1365,7 @@
   "Showing_archived_results": "<p>Showing <b>%s</b> archived results</p>",
   "Showing_online_users": "Showing: <b>__total_showing__</b>, Online: __online__, Total: __total__ users",
   "Showing_results": "<p>Showing <b>%s</b> results</p>",
+  "Sign_in_to_start_talking": "Sign in to start talking",
   "since_creation": "since %s",
   "Site_Name": "Site Name",
   "Site_Url": "Site URL",
@@ -1716,4 +1719,4 @@
   "your_message_optional": "your message (optional)",
   "Your_password_is_wrong": "Your password is wrong!",
   "Your_push_was_sent_to_s_devices": "Your push was sent to %s devices"
-}
+}
\ No newline at end of file
diff --git a/packages/rocketchat-i18n/i18n/es.i18n.json b/packages/rocketchat-i18n/i18n/es.i18n.json
index d1af87871a109630a7913ef1bd8ba201295dd6ef..e5dceb0f4260ca4bede8b1b3338d7e963e327b8c 100644
--- a/packages/rocketchat-i18n/i18n/es.i18n.json
+++ b/packages/rocketchat-i18n/i18n/es.i18n.json
@@ -1227,7 +1227,6 @@
   "Unmute_user": "Des-silenciar usuario",
   "Unnamed": "Sin nombre",
   "Unpin_Message": "Desfijar Mensaje",
-  "Unread_Alert": "Alerta de no leídos",
   "Unread_Rooms": "Salas sin leer",
   "Unread_Rooms_Mode": "Modo Salas sin leer",
   "Unstar_Message": "Eliminar Destacado",
diff --git a/packages/rocketchat-i18n/i18n/fr.i18n.json b/packages/rocketchat-i18n/i18n/fr.i18n.json
index 8edb98662179f5a44ac442f75d41b7a144eff9a8..7d9b43e0c9fcb5533db0a5d4bffa9cd8a4ecb787 100644
--- a/packages/rocketchat-i18n/i18n/fr.i18n.json
+++ b/packages/rocketchat-i18n/i18n/fr.i18n.json
@@ -337,13 +337,13 @@
   "Custom": "Personnalisé",
   "Custom_Emoji": "Emoticône personnalisée",
   "Custom_Emoji_Add": "Ajouter une nouvelle émoticône",
-  "Custom_Emoji_Added_Successfully": "Emoji personnalisé ajouté avec succès",
-  "Custom_Emoji_Delete_Warning": "Effacer un emoji ne peut pas être annulé.",
-  "Custom_Emoji_Error_Invalid_Emoji": "émoticône non valide",
-  "Custom_Emoji_Error_Name_Or_Alias_Already_In_Use": "L'émoji personnalisé ou un de ses alias est déjà en cours d'utilisation.",
-  "Custom_Emoji_Has_Been_Deleted": "L'émoji personnalisé a été effacé.",
-  "Custom_Emoji_Info": "Information sur l'émoji personnalisé",
-  "Custom_Emoji_Updated_Successfully": "Emoji personnalisé mis à jour avec succès",
+  "Custom_Emoji_Added_Successfully": "Emoticône personnalisée ajouté avec succès",
+  "Custom_Emoji_Delete_Warning": "Effacer une  émoticône est irréversible.",
+  "Custom_Emoji_Error_Invalid_Emoji": "Emoticône non valide",
+  "Custom_Emoji_Error_Name_Or_Alias_Already_In_Use": "L'émoticône personnalisée ou un de ses alias est déjà en cours d'utilisation.",
+  "Custom_Emoji_Has_Been_Deleted": "L'émoticône personnalisée a été effacé.",
+  "Custom_Emoji_Info": "Information sur l'émoticône personnalisée",
+  "Custom_Emoji_Updated_Successfully": "Emoticône personnalisée mise à jour avec succès",
   "Custom_Fields": "Champs personnalisés",
   "Custom_oauth_helper": "Lorsque vous configurez votre service OAuth, vous devez indiquer une URL pour le Callback. Utilisez <pre>%s</pre>",
   "Custom_oauth_unique_name": "Nom unique de l'OAuth personnalisé",
@@ -366,16 +366,16 @@
   "Date_From": "De",
   "Date_to": "à",
   "days": "jours",
-  "DB_Migration": "Base de données de migration",
-  "DB_Migration_Date": "Base de données de migration date",
+  "DB_Migration": "Mise à jour de la base de données",
+  "DB_Migration_Date": "Date de mise à jour de la base de données",
   "Deactivate": "Désactiver",
   "Decline": "Refuser",
   "Default": "Défaut",
   "Delete": "Supprimer",
-  "Delete_message": "Suppression de messages",
+  "Delete_message": "Supprimer le message",
   "Delete_my_account": "Supprimer mon compte",
   "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 ne peut être annulée.",
+  "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é",
@@ -393,7 +393,7 @@
   "Direct_Messages": "Messages Privés",
   "Display_offline_form": "Affichage formulaire hors ligne",
   "Displays_action_text": "Texte d'affichage",
-  "Do_you_want_to_change_to_s_question": "Voulez-vous changer pour <strong>%s</strong> ?",
+  "Do_you_want_to_change_to_s_question": "Voulez-vous changer pour <strong>%s</strong> ?",
   "Domain": "Domaine",
   "Domain_added": "Domaine ajouté",
   "Domain_removed": "Domaine supprimé",
@@ -404,7 +404,7 @@
   "Dry_run_description": "Envoi un unique e-mail, à l'adresse de l'expéditeur. L'adresse e-mail doit appartenir à un utilisateur valide.",
   "Duplicate_archived_channel_name": "Un canal archivé avec le nom '#%s' existe",
   "Duplicate_archived_private_group_name": "Un groupe privé archivé avec le nom '%s' existe",
-  "Duplicate_channel_name": "Un canal avec le nom '% s' existe",
+  "Duplicate_channel_name": "Un canal avec le nom '%s' existe",
   "Duplicate_private_group_name": "Un groupe privé avec le nom '%s' existe déjà",
   "Duration": "Durée",
   "Edit": "Modifier",
@@ -419,9 +419,9 @@
   "Email_already_exists": "L'adresse e-mail existe déjà",
   "Email_body": "Corps du message",
   "Email_Change_Disabled": "Votre administrateur de Rocket.Chat a désactivé le changement d'adresse e-mail",
-  "Email_Footer_Description": "Vous pouvez utiliser les variables suivantes: <br /><ul><li> [Site_Name] et [Site_URL] pour le nom de l'application et URL respectivement. </li></ul>",
+  "Email_Footer_Description": "Vous pouvez utiliser les variables suivantes: <br/><ul><li> [Site_Name] et [Site_URL] pour le nom de l'application et URL respectivement. </li></ul>",
   "Email_from": "De",
-  "Email_Header_Description": "Vous pouvez utiliser les variables suivantes: <br /><ul><li> [Site_Name] et [Site_URL] pour le nom de l'application et URL respectivement. </li></ul>",
+  "Email_Header_Description": "Vous pouvez utiliser les variables suivantes: <br/><ul><li> [Site_Name] et [Site_URL] pour le nom de l'application et URL respectivement. </li></ul>",
   "Email_Notification_Mode": "Notifications hors-ligne par e-mail",
   "Email_Notification_Mode_All": "Toutes les Mentions/MP",
   "Email_Notification_Mode_Disabled": "Désactivé",
@@ -429,7 +429,7 @@
   "Email_subject": "Sujet",
   "Email_verified": "Adresse e-mail vérifiée",
   "Emoji": "Emoticône ",
-  "EmojiCustomFilesystem": "Système de fichier d'émoticones personnalisés",
+  "EmojiCustomFilesystem": "Système de fichier d'émoticônes personnalisés",
   "Empty_title": "Titre vide",
   "Enable": "Activer",
   "Enable_Desktop_Notifications": "Activer les notifications sur le bureau",
@@ -467,7 +467,7 @@
   "error-file-too-large": "Le fichier est trop lourd",
   "error-importer-not-defined": "L'importateur n'a pas été défini correctement, il manque la classe Import.",
   "error-input-is-not-a-valid-field": "__input__ n'est pas un __field__ valide",
-  "error-invalid-actionlink": "lien d'action non valide",
+  "error-invalid-actionlink": "Lien d'action non valide",
   "error-invalid-arguments": "Arguments non valides",
   "error-invalid-asset": "Ressource invalide",
   "error-invalid-channel": "Canal non valide.",
@@ -625,7 +625,7 @@
   "If_you_are_sure_type_in_your_username": "Si vous êtes certain(e), saisissez votre nom d'utilisateur :",
   "Iframe_Integration": "Intégration Iframe",
   "Iframe_Integration_receive_enable": "Activer la réception",
-  "Iframe_Integration_receive_enable_Description": "Autoriser la fenêtre parente à envoyer des commandes à Rocket.Chat",
+  "Iframe_Integration_receive_enable_Description": "Autoriser la fenêtre parente à envoyer des commandes à Rocket.Chat.",
   "Iframe_Integration_receive_origin": "Recevoir les origines",
   "Iframe_Integration_receive_origin_Description": "Seules les pages ayant une origine donnée seront autorisée à envoyer des commandes ou `*` pour toutes les origines. Vous pouvez utiliser de multiples valeurs séparées par des` ,`. Exemple : `http://localhost,https://localhost`",
   "Iframe_Integration_send_enable": "Activer l'envoi",
@@ -646,11 +646,11 @@
   "Importer_Prepare_Restart_Import": "Recommencer l’Importation",
   "Importer_Prepare_Start_Import": "Commencer l'Importation",
   "Importer_Prepare_Uncheck_Archived_Channels": "Désélectionner les canaux archivés",
-  "Importer_Prepare_Uncheck_Deleted_Users": "Désélectionner les Utilisateurs Supprimés",
+  "Importer_Prepare_Uncheck_Deleted_Users": "Désélectionner les utilisateurs supprimés",
   "Importer_progress_error": "Impossible d'obtenir l'état de l'importation.",
   "Importer_setup_error": "Une erreur est survenue lors du paramétrage de l'importateur.",
   "Importer_Source_File": "Sélection du fichier source",
-  "Incoming_Livechats": "Arrivée de nouveaux Livechats",
+  "Incoming_Livechats": "Arrivée de nouveaux chats en direct",
   "inline_code": "Ligne de code",
   "Install_Extension": "Installer l'extension",
   "Install_FxOs": "Installez Rocket.Chat dans votre Firefox",
@@ -696,6 +696,7 @@
   "Invalid_pass": "Le mot de passe doit être renseigné",
   "Invalid_room_name": "<strong>%s</strong> n'est pas un nom de salon valide.<br/>Utilisez uniquement des lettres, des chiffres et des tirets (milieu et bas)",
   "Invalid_secret_URL_message": "L'URL fournie est invalide.",
+  "Invalid_setting_s": "Paramètre invalide : %s",
   "invisible": "invisible",
   "Invisible": "Invisible",
   "Invitation": "Invitation",
@@ -739,11 +740,11 @@
   "Jump_to_message": "Aller au message",
   "Jump_to_recent_messages": "Aller aux messages récents",
   "Katex_Dollar_Syntax": "Autoriser Dollar Syntaxe",
-  "Katex_Dollar_Syntax_Description": "Autoriser avec $$ bloc Katex $$ et $ Katex inline $ syntaxes",
+  "Katex_Dollar_Syntax_Description": "Autoriser les syntaxes : $$bloc katex$$ et $katex inline$",
   "Katex_Enabled": "Katex activé",
   "Katex_Enabled_Description": "Autoriser l' utilisation <a target=\"_blank\" href=\"http://khan.github.io/KaTeX/\">Katex</a> pour les mathématiques photocomposition dans les messages",
   "Katex_Parenthesis_Syntax": "Autoriser Parenthesis Syntaxe",
-  "Katex_Parenthesis_Syntax_Description": "Autoriser l'utilisation \\ [bloc Katex \\] et \\ (inline Katex \\) syntaxes",
+  "Katex_Parenthesis_Syntax_Description": "Autoriser les syntaxes : \\[bloc katex\\] et \\(inline katex\\)",
   "Knowledge_Base": "Base de connaissances",
   "Label": "Label",
   "Language": "Langue",
@@ -825,7 +826,6 @@
   "Domains_allowed_to_embed_the_livechat_widget": "Liste des domaines autorisés à intégrer le widget de chat en direct, séparés par des virgules. Laissez vide pour autoriser tous les domaines. ",
   "Livechat_Dashboard": "Tableau de bord du chat en direct",
   "Livechat_enabled": "Chat en direct activé",
-  "Livechat_forward_open_chats": "chats Forward ouverts",
   "Livechat_forward_open_chats_timeout": "Timeout (en secondes) de transmettre les chats",
   "Livechat_guest_count": "Compteur d'invités",
   "Livechat_Inquiry_Already_Taken": "Demande de chat en direct déjà prise en compte",
@@ -906,7 +906,7 @@
   "Message_GroupingPeriodDescription": "Les messages seront regroupés avec les messages précédents si ils sont du même utilisateur et si le temps écoulé est inférieur au temps indiqué en secondes.",
   "Message_HideType_au": "Masquer les messages \"Utilisateur ajouté\"",
   "Message_HideType_mute_unmute": "Masquer les messages \"Utilisateur rendu muet / a retrouvé la parole\"",
-  "Message_HideType_ru": "Masquer les messages \"Utilisateur ejecté\"",
+  "Message_HideType_ru": "Masquer les messages \"Utilisateur éjecté\"",
   "Message_HideType_uj": "Masquer les messages \"L'utilisateur a rejoint\"",
   "Message_HideType_ul": "Masquer les messages \"L'utilisateur a quitté\"",
   "Message_KeepHistory": "Conserver l'historique des messages",
@@ -928,7 +928,7 @@
   "Message_VideoRecorderEnabled": "Enregistreur vidéo activé",
   "Message_VideoRecorderEnabledDescription": "Nécessite que le type de média \"video/webm\" soit accepté dans les paramètres d'envoi des fichiers.",
   "Messages": "Messages",
-  "Messages_that_are_sent_to_the_Incoming_WebHook_will_be_posted_here": "Les messages envoyés au WebHook Entrant seront postés ici",
+  "Messages_that_are_sent_to_the_Incoming_WebHook_will_be_posted_here": "Les messages envoyés au WebHook Entrant seront postés ici.",
   "Meta": "Meta",
   "Meta_fb_app_id": "App ID Facebook",
   "Meta_google-site-verification": "Google Site Verification",
@@ -1008,13 +1008,13 @@
   "Office_hours_updated": "Heures de bureau modifiées",
   "Offline": "Hors ligne",
   "Offline_DM_Email": "Vous avez reçu des messages privés de __user__",
-  "Offline_form": "forme Hors ligne",
-  "Offline_form_unavailable_message": "forme Offline Message indisponible",
-  "Offline_Link_Message": "Aller au message",
+  "Offline_form": "Formulaire hors ligne",
+  "Offline_form_unavailable_message": "Message indisponible du formulaire hors ligne",
+  "Offline_Link_Message": "ALLER AU MESSAGE",
   "Offline_Mention_Email": "Vous avez été mentionné par __user__ dans le salon #__room__",
-  "Offline_message": "un message Hors ligne",
-  "Offline_success_message": "message de succès hors ligne",
-  "Offline_unavailable": "Offline indisponible",
+  "Offline_message": "Message hors ligne",
+  "Offline_success_message": "Message de succès hors ligne",
+  "Offline_unavailable": "Hors ligne indisponible",
   "On": "Allumé",
   "Online": "Connecté",
   "Only_On_Desktop": "Mode Bureau (envoyé seulement quand Entrée sur le bureau)",
@@ -1022,7 +1022,7 @@
   "Oops!": "Oups",
   "Open": "Ouvrerture",
   "Open_days_of_the_week": "Jours d'ouverture",
-  "Open_Livechats": "Ouvrir les Livechats",
+  "Open_Livechats": "Ouvrir les chats en direct",
   "Opened": "Ouvert",
   "Opened_in_a_new_window": "Ouvert dans une nouvelle fenêtre.",
   "Opens_a_channel_group_or_direct_message": "Ouvre un canal, un groupe ou un message direct",
@@ -1060,27 +1060,27 @@
   "PiwikAnalytics_url_Description": "L'URL où le Piwik réside, assurez-vous d'inclure la barre trialing. Exemple: //piwik.rocket.chat/",
   "Placeholder_for_email_or_username_login_field": "Texte de remplacement pour l'adresse e-mail ou le nom d'utilisateur à la connexion",
   "Placeholder_for_password_login_field": "Texte de remplacement pour le mot de passe",
-  "Please_add_a_comment": "S'il vous plaît ajouter un commentaire",
+  "Please_add_a_comment": "Merci d'ajouter un commentaire",
   "Please_add_a_comment_to_close_the_room": "Merci d'ajouter un commentaire pour fermer le salon",
-  "Please_answer_survey": "S'il vous plait , prenez un moment pour répondre à un court sondage à propos de ce chat ",
+  "Please_answer_survey": "Merci de prendre un moment pour répondre à un court sondage à propos de cette conversation",
   "Please_enter_value_for_url": "Veuillez entrer l'url de votre avatar.",
   "Please_enter_your_new_password_below": "Veuillez écrire votre nouveau mot de passe ci-dessous :",
-  "Please_enter_your_password": "Veuillez ré-entrer votre mot de passe",
+  "Please_enter_your_password": "Veuillez entrer votre mot de passe",
   "please_enter_valid_domain": "Merci d'entrer un domaine valide",
   "Please_fill_a_label": "Veuillez entrer une étiquette",
   "Please_fill_a_name": "Veuillez saisir un nom",
   "Please_fill_a_username": "Veuillez enter un nom d'utilisateur",
   "Please_fill_name_and_email": "Veuillez remplir le nom et l'adresse e-mail",
   "Please_select_an_user": "Merci de sélectionner un utilisateur",
-  "Please_select_enabled_yes_or_no": "Veuillez choisir une option pour Activé",
+  "Please_select_enabled_yes_or_no": "Veuillez choisir une option pour \"Activé\"",
   "Please_wait": "Veuillez patienter",
   "Please_wait_activation": "Veuillez patienter, cela peut prendre un peu de temps.",
   "Please_wait_while_OTR_is_being_established": "Veuillez patienter pendant l'établissement de l'OTR",
-  "Please_wait_while_your_account_is_being_deleted": "Veuillez patienter pendant que votre compte est supprimé...",
-  "Please_wait_while_your_profile_is_being_saved": "Veuillez patienter pendant que votre profil est enregistré ...",
+  "Please_wait_while_your_account_is_being_deleted": "Veuillez patienter pendant la suppression de votre compte...",
+  "Please_wait_while_your_profile_is_being_saved": "Veuillez patienter pendant l'enregistrement de votre profil...",
   "Port": "Port",
   "Post_as": "Publié en tant que",
-  "Post_to_Channel": "Publié sur le Canal",
+  "Post_to_Channel": "Publié sur le canal",
   "Post_to_s_as_s": "Publié sur <strong>%s</strong> en tant que <strong>%s</strong>",
   "Preferences": "Préférences",
   "Preferences_saved": "Préférences enregistrées",
@@ -1323,6 +1323,7 @@
   "Stats_Total_Messages": "Nombre total de messages",
   "Stats_Total_Messages_Channel": "Nombre total de messages dans les canaux",
   "Stats_Total_Messages_Direct": "Nombre total de messages dans les discussions privées",
+  "Stats_Total_Messages_Livechat": "Nombre total de messages de chat en direct",
   "Stats_Total_Private_Groups": "Nombre total de groupes privés",
   "Stats_Total_Rooms": "Nombre total de salons",
   "Stats_Total_Users": "Nombre total d'utilisateurs",
@@ -1379,10 +1380,10 @@
   "theme-color-unread-notification-color": "Couleur des notifications non-lues",
   "theme-custom-css": "CSS personnalisé",
   "theme-font-body-font-family": "Famille de police du body",
-  "There_are_no_agents_added_to_this_department_yet": "Il n'y a pas d'assistant ajouté à ce département pour le moment",
+  "There_are_no_agents_added_to_this_department_yet": "Il n'y a pas d'assistant ajouté à ce département pour le moment.",
   "There_are_no_integrations": "Il n'y a aucune intégration",
   "There_are_no_users_in_this_role": "Il n'y a aucun utilisateur avec ce rôle.",
-  "This_conversation_is_already_closed": "Ces conversation a déjà été fermée.",
+  "This_conversation_is_already_closed": "Cette conversation a déjà été fermée.",
   "This_email_has_already_been_used_and_has_not_been_verified__Please_change_your_password": "Cette adresse e-mail a déjà été utilisée et n'a pas été vérifiée. Veuillez changer votre mot de passe.",
   "This_is_a_desktop_notification": "Ceci est une notification sur le bureau.",
   "This_is_a_push_test_messsage": "Ceci est une notification de test",
@@ -1422,7 +1423,6 @@
   "Unmute_user": "Rendre la parole",
   "Unnamed": "Sans nom",
   "Unpin_Message": "Désépingler ce message",
-  "Unread_Alert": "Alerte non lue",
   "Unread_Messages": "Messages non lus",
   "Unread_Rooms": "Salons contenant des messages non-lus",
   "Unread_Rooms_Mode": "Mode des salons non-lus",
@@ -1431,10 +1431,10 @@
   "Upload_file_name": "Nom du fichier",
   "Upload_file_question": "Envoyer le fichier ?",
   "Uploading_file": "Envoi du fichier en cours...",
-  "Uptime": "uptime",
+  "Uptime": "Durée de fonctionnement",
   "URL": "URL",
   "Use_account_preference": "Préférence du compte utilisateur",
-  "Use_Emojis": "Utiliser les Emojis",
+  "Use_Emojis": "Utiliser les émoticônes",
   "Use_Global_Settings": "Utiliser les paramètres globaux",
   "Use_initials_avatar": "Utiliser les initiales de votre nom d'utilisateur",
   "Use_service_avatar": "Utiliser l'avatar %s",
@@ -1446,10 +1446,10 @@
   "User__username__is_now_a_owner_of__room_name_": "L'utilisateur __username__ est désormais un propriétaire du salon __room_name__",
   "User__username__removed_from__room_name__moderators": "L'utilisateur __username__ n'est plus modérateur du salon __room_name__.",
   "User__username__removed_from__room_name__owners": "L'utilisateur __username__ n'est plus propriétaire du salon __room_name__.",
-  "User_added": "L'utilisateur <em>__user_added__</em> a été ajouté",
+  "User_added": "Utilisateur ajouté",
   "User_added_by": "L'utilisateur <em>__user_added__</em> a été ajouté par <em>__user_by__</em>.",
   "User_added_successfully": "Utilisateur ajouté avec succès",
-  "User_doesnt_exist": "Aucun utilisateur nommé `@%s` existe.",
+  "User_doesnt_exist": "Aucun utilisateur nommé `@%s` existant.",
   "User_has_been_activated": "L'utilisateur a été activé",
   "User_has_been_deactivated": "L'utilisateur a été désactivé",
   "User_has_been_deleted": "L'utilisateur a été supprimé",
diff --git a/packages/rocketchat-i18n/i18n/he.i18n.json b/packages/rocketchat-i18n/i18n/he.i18n.json
index 459b1008f5f8bfc46174c6b47d8b9809a0a9b2e6..b912f90ea58d43bb8bf104d0a775c82f666ca581 100644
--- a/packages/rocketchat-i18n/i18n/he.i18n.json
+++ b/packages/rocketchat-i18n/i18n/he.i18n.json
@@ -1097,7 +1097,6 @@
   "Unmute_user": "בטל השתקת משתמש",
   "Unnamed": "ללא שם",
   "Unpin_Message": "שחרור הודעה",
-  "Unread_Alert": "התרעה על לא נקרא",
   "Unread_Rooms": "חדרים שלא נקראו",
   "Unread_Rooms_Mode": "מצב חדרים שלא נקראו",
   "Unstar_Message": "הסר ממועדפים",
diff --git a/packages/rocketchat-i18n/i18n/hr.i18n.json b/packages/rocketchat-i18n/i18n/hr.i18n.json
index 59f172d3704d1f6be1dceb736f6a44accf1e4b3a..83e9f1a937d0ca6823ce174ed3dd834338c26f59 100644
--- a/packages/rocketchat-i18n/i18n/hr.i18n.json
+++ b/packages/rocketchat-i18n/i18n/hr.i18n.json
@@ -1292,7 +1292,6 @@
   "Unmute_user": "Uključi korisnika",
   "Unnamed": "Neimenovano",
   "Unpin_Message": "Otkvači Poruku",
-  "Unread_Alert": "Nepročitane obavijesti",
   "Unread_Rooms": "Nepročitane Sobe",
   "Unread_Rooms_Mode": "Mod Nepročitanih Soba",
   "Unstar_Message": "Ukloni zvjezdicu",
diff --git a/packages/rocketchat-i18n/i18n/it.i18n.json b/packages/rocketchat-i18n/i18n/it.i18n.json
index af5735a40eb18d0d381314c282e1cf5fd1aed165..06d62a2f4d7fc6289056fd013ef88e3cb6495c85 100644
--- a/packages/rocketchat-i18n/i18n/it.i18n.json
+++ b/packages/rocketchat-i18n/i18n/it.i18n.json
@@ -1482,7 +1482,6 @@
   "Unmute_user": "Togli il muto all'utente",
   "Unnamed": "Senza nome",
   "Unpin_Message": "Rimuovi il pin dal messaggio",
-  "Unread_Alert": "Avviso Non Letto",
   "Unread_Rooms": "Stanze non letti",
   "Unread_Rooms_Mode": "Modalità Stanze Non Letta",
   "Unstar_Message": "Rimuovi segnalibro",
diff --git a/packages/rocketchat-i18n/i18n/ja.i18n.json b/packages/rocketchat-i18n/i18n/ja.i18n.json
index 8cc4b4e22f4ef63c61d1718ad5446aeb098db92c..d207cdff4f02c0f98c34490cc3f494e9b4044463 100644
--- a/packages/rocketchat-i18n/i18n/ja.i18n.json
+++ b/packages/rocketchat-i18n/i18n/ja.i18n.json
@@ -1103,7 +1103,6 @@
   "Unmute_user": "ミュートを解除",
   "Unnamed": "無名",
   "Unpin_Message": "ピン留めを外す",
-  "Unread_Alert": "未読アラート",
   "Unread_Rooms": "未読のあるルーム",
   "Unread_Rooms_Mode": "未読ルーム表示モード",
   "Unstar_Message": "スターを外す",
diff --git a/packages/rocketchat-i18n/i18n/ko.i18n.json b/packages/rocketchat-i18n/i18n/ko.i18n.json
index ca1ae1f28e183a0469a480c2798377f7526ca833..02492e8aa80cd41ca749842fdf2d6630aa591094 100644
--- a/packages/rocketchat-i18n/i18n/ko.i18n.json
+++ b/packages/rocketchat-i18n/i18n/ko.i18n.json
@@ -16,7 +16,7 @@
   "Accessing_permissions": "권한 액세스",
   "Account_SID": "계정 SID",
   "Accounts": "계정",
-  "Accounts_AllowDeleteOwnAccount": "사용자가 자신의 계정을 삭제할 수있습니다.",
+  "Accounts_AllowDeleteOwnAccount": "사용자가 자신의 계정을 삭제할 수 있습니다.",
   "Accounts_AllowedDomainsList": "허용된 도메인 목록",
   "Accounts_AllowedDomainsList_Description": "허용된 도메인을 쉼표(,)로 구분하기",
   "Accounts_AllowEmailChange": "이메일 변경을 허용합니다",
@@ -203,6 +203,8 @@
   "AutoLinker_Urls_www": "AutoLinker 'WWW'의 URL",
   "AutoLinker_UrlsRegExp": "AutoLinker URL 정규 표현식",
   "Automatic_Translation": "자동 번역",
+  "Auto_Translate": "자동 번역",
+  "AutoTranslate_Enabled": "자동 번역 활성화",
   "AutoTranslate_GoogleAPIKey": "구글 API 키",
   "Available": "유효한",
   "Available_agents": "사용 가능한 에이전트",
@@ -221,9 +223,12 @@
   "Back_to_integrations": "위로 통합에",
   "Back_to_login": "로그인으로 돌아가기",
   "Back_to_permissions": "돌아 가기 권한",
+  "Block_User": "사용자 차단",
   "Body": "ì‹ ì²´",
   "bold": "굵게",
+  "BotHelpers_userFields": "사용자 필드",
   "Branch": "분기",
+  "Bugsnag_api_key": "Bugsnag API 키",
   "busy": "바쁨",
   "Busy": "바쁨",
   "busy_female": "바쁨",
@@ -231,9 +236,14 @@
   "busy_male": "바쁨",
   "Busy_male": "바쁨",
   "by": "으로",
+  "cache_cleared": "캐시가 삭제됨",
   "Cancel": "취소",
   "Cancel_message_input": "취소",
   "Cannot_invite_users_to_direct_rooms": "객실을 지시하는 사용자를 초대 할 수 없습니다",
+  "CAS_autoclose": "로그인 팝업을 자동으로 닫음",
+  "CAS_button_color": "로그인 버튼 배경 색상",
+  "CAS_button_label_color": "로그인 버튼 텍스트 색상",
+  "CAS_button_label_text": "로그인 버튼 레이블",
   "CDN_PREFIX": "CDN Prefix",
   "Certificates_and_Keys": "인증서와 키",
   "Changing_email": "변경 이메일",
diff --git a/packages/rocketchat-i18n/i18n/pl.i18n.json b/packages/rocketchat-i18n/i18n/pl.i18n.json
index 85a9d5cfc41444c0819f1c94eee910ffe715e7db..28492fd71c39f769039508c18eef64a56c00c46a 100644
--- a/packages/rocketchat-i18n/i18n/pl.i18n.json
+++ b/packages/rocketchat-i18n/i18n/pl.i18n.json
@@ -1202,7 +1202,6 @@
   "Unmute_user": "Anuluj wyciszenie użytkownika",
   "Unnamed": "Anonimowy",
   "Unpin_Message": "Odepnij wiadomość",
-  "Unread_Alert": "Alarm nieprzeczytany",
   "Unread_Rooms": "Nieprzeczytane pokoje",
   "Unread_Rooms_Mode": "Tryb nieprzeczytanych pokoi",
   "Unstar_Message": "Usuń oznaczenie",
diff --git a/packages/rocketchat-i18n/i18n/sv.i18n.json b/packages/rocketchat-i18n/i18n/sv.i18n.json
index 92c5f703e5d6dc7670a27884dd73cc0926c65f2e..b677c0687450cad1c82971067170e86e4d5e398c 100644
--- a/packages/rocketchat-i18n/i18n/sv.i18n.json
+++ b/packages/rocketchat-i18n/i18n/sv.i18n.json
@@ -421,6 +421,7 @@
   "Field": "Fält",
   "Field_removed": "fältet avlägsnas",
   "File_exceeds_allowed_size_of_bytes": "Filen överskrider tillåten storlek __size__ bytes",
+  "File_uploaded": "Uppladdad fil",
   "FileUpload": "Uppladdad fil",
   "FileUpload_Enabled": "Filuppladdningar aktiverade",
   "FileUpload_File_Empty": "Tom fil",
@@ -506,6 +507,7 @@
   "Integration_added": "Integrationen har lagts",
   "Integration_Incoming_WebHook": "Inkommande WebHook Integration",
   "Integration_New": "Ny integrering",
+  "Integrations_Outgoing_Type_FileUploaded": "Uppladdad fil",
   "Integration_Outgoing_WebHook": "Utgående WebHook Integration",
   "Integration_updated": "Integrationen har uppdaterats",
   "Integrations": "Integreringar",
@@ -1103,6 +1105,8 @@
   "Unread_Rooms": "Olästa rum",
   "Unread_Rooms_Mode": "Olästa Rum Läge",
   "Unstar_Message": "Ta bort stjärnmarkering",
+  "Upload_file_description": "Filbeskrivning",
+  "Upload_file_name": "Filnamn",
   "Upload_file_question": "Ladda upp fil?",
   "Uploading_file": "Laddar upp fil...",
   "Uptime": "drifttid",
diff --git a/packages/rocketchat-i18n/i18n/zh.i18n.json b/packages/rocketchat-i18n/i18n/zh.i18n.json
index 10364945aa105887379f839e00182e14ca08ea2b..00b01cda799a2d11ed22421715aaecef70efc298 100644
--- a/packages/rocketchat-i18n/i18n/zh.i18n.json
+++ b/packages/rocketchat-i18n/i18n/zh.i18n.json
@@ -1341,7 +1341,6 @@
   "Unmute_user": "取消禁言",
   "Unnamed": "未命名",
   "Unpin_Message": "取消固定",
-  "Unread_Alert": "未读警报",
   "Unread_Messages": "未读消息",
   "Unread_Rooms": "未读房间",
   "Unread_Rooms_Mode": "未读房间模式",
diff --git a/packages/rocketchat-importer-csv/server.js b/packages/rocketchat-importer-csv/server.js
index 183346fb358e74df4b915e55d0378dcb5167f543..f701075e6a6cb9c85d33dd65549c7f3d9886d4cc 100644
--- a/packages/rocketchat-importer-csv/server.js
+++ b/packages/rocketchat-importer-csv/server.js
@@ -122,9 +122,9 @@ Importer.CSV = class ImporterCSV extends Importer.Base {
 		super.updateRecord({ 'count.messages': messagesCount, 'messagesstatus': null });
 		super.addCountToTotal(messagesCount);
 
-		//Ensure we have some users, channels, and messages
-		if (tempUsers.length === 0 || tempChannels.length === 0 || messagesCount === 0) {
-			this.logger.warn(`The loaded users count ${ tempUsers.length }, the loaded channels ${ tempChannels.length }, and the loaded messages ${ messagesCount }`);
+		//Ensure we have at least a single user, channel, or message
+		if (tempUsers.length === 0 && tempChannels.length === 0 && messagesCount === 0) {
+			this.logger.error('No users, channels, or messages found in the import file.');
 			super.updateProgress(Importer.ProgressStep.ERROR);
 			return super.getProgress();
 		}
diff --git a/packages/rocketchat-katex/katex.coffee b/packages/rocketchat-katex/katex.coffee
deleted file mode 100644
index 6e7973f943773960149c0a4a20e70c827ef0db84..0000000000000000000000000000000000000000
--- a/packages/rocketchat-katex/katex.coffee
+++ /dev/null
@@ -1,181 +0,0 @@
-###
-# KaTeX is a fast, easy-to-use JavaScript library for TeX math rendering on the web.
-# https://github.com/Khan/KaTeX
-###
-
-katex = require('katex')
-
-class Katex
-	constructor: ->
-		@delimiters_map = [
-			{ opener: '\\[', closer: '\\]', displayMode: true , enabled: () => @parenthesis_syntax_enabled() },
-			{ opener: '\\(', closer: '\\)', displayMode: false, enabled: () => @parenthesis_syntax_enabled() },
-			{ opener: '$$' , closer: '$$' , displayMode: true , enabled: () => @dollar_syntax_enabled() },
-			{ opener: '$'  , closer: '$'  , displayMode: false, enabled: () => @dollar_syntax_enabled() },
-		]
-
-	# Searches for the first opening delimiter in the string from a given position
-	find_opening_delimiter: (str, start) -> # Search the string for each opening delimiter
-		matches = ({options: o, pos: str.indexOf(o.opener, start)} for o in @delimiters_map when o.enabled())
-		positions = (m.pos for m in matches when m.pos >= 0)
-
-		# No opening delimiters were found
-		if positions.length == 0
-			return null
-
-		# Take the first delimiter found
-		pos = Math.min.apply Math, positions
-
-		match_index = (m.pos for m in matches).indexOf(pos)
-		match = matches[match_index]
-
-		return match
-
-	class Boundary
-		length: ->
-			return @end - @start
-
-		extract: (str) ->
-			return str.substr @start, @length()
-
-	# Returns the outer and inner boundaries of the latex block starting
-	# at the given opening delimiter
-	get_latex_boundaries: (str, opening_delimiter_match) ->
-		inner = new Boundary
-		outer = new Boundary
-
-		# The closing delimiter matching to the opening one
-		closer = opening_delimiter_match.options.closer
-
-		outer.start = opening_delimiter_match.pos
-		inner.start = opening_delimiter_match.pos + closer.length
-
-		# Search for a closer delimiter after the opening one
-		closer_index = str.substr(inner.start).indexOf(closer)
-		if closer_index < 0
-			return null
-
-		inner.end = inner.start + closer_index
-		outer.end = inner.end + closer.length
-
-		return {
-			outer: outer
-			inner: inner
-		}
-
-	# Searches for the first latex block in the given string
-	find_latex: (str) ->
-		start = 0
-		while (opening_delimiter_match = @find_opening_delimiter str, start++)?
-
-			match = @get_latex_boundaries str, opening_delimiter_match
-
-			if match?.inner.extract(str).trim().length
-				match.options = opening_delimiter_match.options
-				return match
-
-		return null
-
-	# Breaks a message to what comes before, after and to the content of a
-	# matched latex block
-	extract_latex: (str, match) ->
-		before = str.substr 0, match.outer.start
-		after  = str.substr match.outer.end
-
-		latex = match.inner.extract str
-		latex = s.unescapeHTML latex
-
-		return { before: before, latex : latex, after : after }
-
-	# Takes a latex math string and the desired display mode and renders it
-	# to HTML using the KaTeX library
-	render_latex: (latex, displayMode) ->
-		try
-			rendered = katex.renderToString latex , {displayMode: displayMode}
-		catch e
-			display_mode = if displayMode then "block" else "inline"
-			rendered =  "<div class=\"katex-error katex-#{display_mode}-error\">"
-			rendered += 	"#{s.escapeHTML e.message}"
-			rendered += "</div>"
-
-		return rendered
-
-	# Takes a string and renders all latex blocks inside it
-	render: (str, render_func) ->
-		result = ''
-
-		loop
-
-			# Find the first latex block in the string
-			match = @find_latex str
-
-			unless match?
-				result += str
-				break
-
-			parts = @extract_latex str, match
-
-			# Add to the reuslt what comes before the latex block as well as
-			# the rendered latex content
-			rendered = render_func parts.latex, match.options.displayMode
-			result += parts.before + rendered
-
-			# Set what comes after the latex block to be examined next
-			str = parts.after
-
-		return result
-
-	# Takes a rocketchat message and renders latex in its content
-	render_message: (message) ->
-		# Render only if enabled in admin panel
-		if @katex_enabled()
-			msg = message
-
-			if not _.isString message
-				if _.trim message.html
-					msg = message.html
-				else
-					return message
-
-			if _.isString message
-				render_func = (latex, displayMode) =>
-					return @render_latex latex, displayMode
-			else
-				message.tokens ?= []
-
-				render_func = (latex, displayMode) =>
-					token = "=!=#{Random.id()}=!="
-
-					message.tokens.push
-						token: token
-						text: @render_latex latex, displayMode
-
-					return token
-
-			msg = @render msg, render_func
-
-			if not _.isString message
-				message.html = msg
-			else
-				message = msg
-
-		return message
-
-	katex_enabled: ->
-		return RocketChat.settings.get('Katex_Enabled')
-
-	dollar_syntax_enabled: ->
-		return RocketChat.settings.get('Katex_Dollar_Syntax')
-
-	parenthesis_syntax_enabled: ->
-		return RocketChat.settings.get('Katex_Parenthesis_Syntax')
-
-
-RocketChat.katex = new Katex
-
-cb = RocketChat.katex.render_message.bind(RocketChat.katex)
-RocketChat.callbacks.add 'renderMessage', cb, RocketChat.callbacks.priority.HIGH - 1, 'katex'
-
-if Meteor.isClient
-	Blaze.registerHelper 'RocketChatKatex', (text) ->
-		return RocketChat.katex.render_message text
diff --git a/packages/rocketchat-katex/katex.js b/packages/rocketchat-katex/katex.js
new file mode 100644
index 0000000000000000000000000000000000000000..234165def86daf55c97234f08947d98f6069dc80
--- /dev/null
+++ b/packages/rocketchat-katex/katex.js
@@ -0,0 +1,255 @@
+/*
+ * KaTeX is a fast, easy-to-use JavaScript library for TeX math rendering on the web.
+ * https://github.com/Khan/KaTeX
+ */
+const katex = require('katex');
+
+class Boundary {
+	constructor() {}
+
+	length() {
+		return this.end - this.start;
+	}
+
+	extract(str) {
+		return str.substr(this.start, this.length());
+	}
+
+}
+
+class Katex {
+	constructor() {
+		this.delimiters_map = [
+			{
+				opener: '\\[',
+				closer: '\\]',
+				displayMode: true,
+				enabled: () => {
+					return this.parenthesis_syntax_enabled();
+				}
+			}, {
+				opener: '\\(',
+				closer: '\\)',
+				displayMode: false,
+				enabled: () => {
+					return this.parenthesis_syntax_enabled();
+				}
+			}, {
+				opener: '$$',
+				closer: '$$',
+				displayMode: true,
+				enabled: () => {
+					return this.dollar_syntax_enabled();
+				}
+			}, {
+				opener: '$',
+				closer: '$',
+				displayMode: false,
+				enabled: () => {
+					return this.dollar_syntax_enabled();
+				}
+			}
+		];
+	}
+	// Searches for the first opening delimiter in the string from a given position
+
+	find_opening_delimiter(str, start) { // Search the string for each opening delimiter
+		const matches = (() => {
+			const map = this.delimiters_map;
+			const results = [];
+
+			map.forEach((op) => {
+				if (op.enabled()) {
+					results.push({
+						options: op,
+						pos: str.indexOf(op.opener, start)
+					});
+				}
+			});
+			return results;
+		})();
+
+		const positions = (() => {
+			const results = [];
+			matches.forEach((pos) => {
+				if (pos.pos >= 0) {
+					results.push(pos.pos);
+				}
+			});
+			return results;
+		})();
+
+		// No opening delimiters were found
+		if (positions.length === 0) {
+			return null;
+		}
+
+		//Take the first delimiter found
+		const pos = Math.min.apply(Math, positions);
+
+		const match_index = (()=> {
+			const results = [];
+			matches.forEach((m) => {
+				results.push(m.pos);
+			});
+			return results;
+		})().indexOf(pos);
+
+		const match = matches[match_index];
+		return match;
+	}
+
+	// Returns the outer and inner boundaries of the latex block starting
+	// at the given opening delimiter
+	get_latex_boundaries(str, opening_delimiter_match) {
+		const inner = new Boundary;
+		const outer = new Boundary;
+
+		// The closing delimiter matching to the opening one
+		const closer = opening_delimiter_match.options.closer;
+		outer.start = opening_delimiter_match.pos;
+		inner.start = opening_delimiter_match.pos + closer.length;
+
+		// Search for a closer delimiter after the opening one
+		const closer_index = str.substr(inner.start).indexOf(closer);
+		if (closer_index < 0) {
+			return null;
+		}
+		inner.end = inner.start + closer_index;
+		outer.end = inner.end + closer.length;
+		return {
+			outer,
+			inner
+		};
+	}
+
+	// Searches for the first latex block in the given string
+	find_latex(str) {
+		let start = 0;
+		let opening_delimiter_match;
+
+		while ((opening_delimiter_match = this.find_opening_delimiter(str, start++)) != null) {
+			const match = this.get_latex_boundaries(str, opening_delimiter_match);
+			if (match && match.inner.extract(str).trim().length) {
+				match.options = opening_delimiter_match.options;
+				return match;
+			}
+		}
+		return null;
+	}
+
+	// Breaks a message to what comes before, after and to the content of a
+	// matched latex block
+	extract_latex(str, match) {
+		const before = str.substr(0, match.outer.start);
+		const after = str.substr(match.outer.end);
+		let latex = match.inner.extract(str);
+		latex = s.unescapeHTML(latex);
+		return {
+			before,
+			latex,
+			after
+		};
+	}
+
+	// Takes a latex math string and the desired display mode and renders it
+	// to HTML using the KaTeX library
+	render_latex(latex, displayMode) {
+		let rendered;
+		try {
+			rendered = katex.renderToString(latex, {
+				displayMode
+			});
+		} catch (error) {
+			const e = error;
+			const display_mode = displayMode ? 'block' : 'inline';
+			rendered = `<div class="katex-error katex-${ display_mode }-error">`;
+			rendered += `${ s.escapeHTML(e.message) }`;
+			rendered += '</div>';
+		}
+		return rendered;
+	}
+
+	// Takes a string and renders all latex blocks inside it
+	render(str, render_func) {
+		let result = '';
+		while (this.find_latex(str) != null) {
+			// Find the first latex block in the string
+			const match = this.find_latex(str);
+			const parts = this.extract_latex(str, match);
+
+			// Add to the reuslt what comes before the latex block as well as
+			// the rendered latex content
+			const rendered = render_func(parts.latex, match.options.displayMode);
+			result += parts.before + rendered;
+			// Set what comes after the latex block to be examined next
+			str = parts.after;
+		}
+		return result += str;
+	}
+
+	// Takes a rocketchat message and renders latex in its content
+	render_message(message) {
+		//Render only if enabled in admin panel
+		let render_func;
+		if (this.katex_enabled()) {
+			let msg = message;
+			if (!_.isString(message)) {
+				if (_.trim(message.html)) {
+					msg = message.html;
+				} else {
+					return message;
+				}
+			}
+			if (_.isString(message)) {
+				render_func = (latex, displayMode) => {
+					return this.render_latex(latex, displayMode);
+				};
+			} else {
+				if (message.tokens == null) {
+					message.tokens = [];
+				}
+				render_func = (latex, displayMode) => {
+					const token = `=!=${ Random.id() }=!=`;
+					message.tokens.push({
+						token,
+						text: this.render_latex(latex, displayMode)
+					});
+					return token;
+				};
+			}
+			msg = this.render(msg, render_func);
+			if (!_.isString(message)) {
+				message.html = msg;
+			} else {
+				message = msg;
+			}
+		}
+		return message;
+	}
+
+	katex_enabled() {
+		return RocketChat.settings.get('Katex_Enabled');
+	}
+
+	dollar_syntax_enabled() {
+		return RocketChat.settings.get('Katex_Dollar_Syntax');
+	}
+
+	parenthesis_syntax_enabled() {
+		return RocketChat.settings.get('Katex_Parenthesis_Syntax');
+	}
+
+}
+
+RocketChat.katex = new Katex;
+
+const cb = RocketChat.katex.render_message.bind(RocketChat.katex);
+
+RocketChat.callbacks.add('renderMessage', cb, RocketChat.callbacks.priority.HIGH - 1, 'katex');
+
+if (Meteor.isClient) {
+	Blaze.registerHelper('RocketChatKatex', function(text) {
+		return RocketChat.katex.render_message(text);
+	});
+}
diff --git a/packages/rocketchat-katex/package.js b/packages/rocketchat-katex/package.js
index 033ea7efeeea8f5826e53cb2e60400781c2abb08..de08f7f50f5de94f95204cc3e3787300d20cc012 100644
--- a/packages/rocketchat-katex/package.js
+++ b/packages/rocketchat-katex/package.js
@@ -6,15 +6,14 @@ Package.describe({
 });
 
 Package.onUse(function(api) {
-	api.use('coffeescript');
 	api.use('ecmascript');
 	api.use('underscore');
 	api.use('templating');
 	api.use('underscorestring:underscore.string');
 	api.use('rocketchat:lib');
 
-	api.addFiles('settings.coffee', 'server');
-	api.addFiles('katex.coffee');
+	api.addFiles('settings.js', 'server');
+	api.addFiles('katex.js');
 	api.addFiles('client/style.css', 'client');
 
 	const katexPath = 'node_modules/katex/dist/';
diff --git a/packages/rocketchat-katex/settings.coffee b/packages/rocketchat-katex/settings.coffee
deleted file mode 100644
index 255aa3dc9c235f0b212def1bf3a0192e1d6c47fc..0000000000000000000000000000000000000000
--- a/packages/rocketchat-katex/settings.coffee
+++ /dev/null
@@ -1,6 +0,0 @@
-Meteor.startup ->
-  enableQuery = {_id: 'Katex_Enabled', value: true}
-  RocketChat.settings.add 'Katex_Enabled', true, {type: 'boolean', group: 'Message', section: 'Katex', public: true, i18n: 'Katex_Enabled_Description'}
-
-  RocketChat.settings.add 'Katex_Parenthesis_Syntax', true, {type: 'boolean', group: 'Message', section: 'Katex', public: true, enableQuery, i18nDescription: 'Katex_Parenthesis_Syntax_Description'}
-  RocketChat.settings.add 'Katex_Dollar_Syntax', false, {type: 'boolean', group: 'Message', section: 'Katex', public: true, enableQuery, i18nDescription: 'Katex_Dollar_Syntax_Description'}
diff --git a/packages/rocketchat-katex/settings.js b/packages/rocketchat-katex/settings.js
new file mode 100644
index 0000000000000000000000000000000000000000..006ef790e64b719e69b51d85ab0bd32b3a687a68
--- /dev/null
+++ b/packages/rocketchat-katex/settings.js
@@ -0,0 +1,32 @@
+Meteor.startup(function() {
+	const enableQuery = {
+		_id: 'Katex_Enabled',
+		value: true
+	};
+	RocketChat.settings.add('Katex_Enabled', true, {
+		type: 'boolean',
+		group: 'Message',
+		section: 'Katex',
+		'public': true,
+		i18n: 'Katex_Enabled_Description'
+	});
+	RocketChat.settings.add('Katex_Parenthesis_Syntax', true, {
+		type: 'boolean',
+		group: 'Message',
+		section: 'Katex',
+		'public': true,
+		enableQuery,
+		i18nDescription: 'Katex_Parenthesis_Syntax_Description'
+	});
+	return RocketChat.settings.add('Katex_Dollar_Syntax', false, {
+		type: 'boolean',
+		group: 'Message',
+		section: 'Katex',
+		'public': true,
+		enableQuery,
+		i18nDescription: 'Katex_Dollar_Syntax_Description'
+	});
+});
+
+// ---
+// generated by coffee-script 1.9.2
diff --git a/packages/rocketchat-lib/client/MessageAction.coffee b/packages/rocketchat-lib/client/MessageAction.coffee
deleted file mode 100644
index 80837c3a0bebe9e4dd587f2dcc35e2d4d9b1251c..0000000000000000000000000000000000000000
--- a/packages/rocketchat-lib/client/MessageAction.coffee
+++ /dev/null
@@ -1,247 +0,0 @@
-import moment from 'moment'
-import toastr from 'toastr'
-
-RocketChat.MessageAction = new class
-	buttons = new ReactiveVar {}
-
-	###
-	config expects the following keys (only id is mandatory):
-		id (mandatory)
-		icon: string
-		i18nLabel: string
-		action: function(event, instance)
-		validation: function(message)
-		order: integer
-	###
-	addButton = (config) ->
-		unless config?.id
-			return false
-
-		Tracker.nonreactive ->
-			btns = buttons.get()
-			btns[config.id] = config
-			buttons.set btns
-
-	removeButton = (id) ->
-		Tracker.nonreactive ->
-			btns = buttons.get()
-			delete btns[id]
-			buttons.set btns
-
-	updateButton = (id, config) ->
-		Tracker.nonreactive ->
-			btns = buttons.get()
-			if btns[id]
-				btns[id] = _.extend btns[id], config
-				buttons.set btns
-
-	getButtonById = (id) ->
-		allButtons = buttons.get()
-		return allButtons[id]
-
-	getButtons = (message, context) ->
-		allButtons = _.toArray buttons.get()
-		if message
-			allowedButtons = _.compact _.map allButtons, (button) ->
-				if not button.context? or button.context.indexOf(context) > -1
-					if not button.validation? or button.validation(message, context)
-						return button
-		else
-			allowedButtons = allButtons
-
-		return _.sortBy allowedButtons, 'order'
-
-	resetButtons = ->
-		buttons.set {}
-
-	getPermaLink = (msgId) ->
-		roomData = ChatSubscription.findOne({rid: Session.get('openedRoom')})
-		if roomData
-			routePath = RocketChat.roomTypes.getRouteLink(roomData.t, roomData)
-		else
-			routePath = document.location.pathname
-		return Meteor.absoluteUrl().replace(/\/$/, '') + routePath + '?msg=' + msgId
-
-	hideDropDown = () ->
-		$('.message-dropdown:visible').hide()
-
-	addButton: addButton
-	removeButton: removeButton
-	updateButton: updateButton
-	getButtons: getButtons
-	getButtonById: getButtonById
-	resetButtons: resetButtons
-	getPermaLink: getPermaLink
-	hideDropDown: hideDropDown
-
-Meteor.startup ->
-
-	$(document).click (event) =>
-		target = $(event.target)
-		if !target.closest('.message-cog-container').length and !target.is('.message-cog-container')
-			RocketChat.MessageAction.hideDropDown()
-
-	RocketChat.MessageAction.addButton
-		id: 'reply-message'
-		icon: 'icon-reply'
-		i18nLabel: 'Reply'
-		context: [
-			'message'
-			'message-mobile'
-		]
-		action: (event, instance) ->
-			message = @_arguments[1]
-			input = instance.find('.input-message')
-			url = RocketChat.MessageAction.getPermaLink(message._id)
-			text = '[ ](' + url + ') @' + message.u.username + ' '
-			if input.value
-				input.value += if input.value.endsWith(' ') then '' else ' '
-			input.value += text
-			input.focus()
-			RocketChat.MessageAction.hideDropDown()
-		validation: (message) ->
-			if not RocketChat.models.Subscriptions.findOne({ rid: message.rid })?
-				return false
-			return true
-		order: 1
-
-	RocketChat.MessageAction.addButton
-		id: 'edit-message'
-		icon: 'icon-pencil'
-		i18nLabel: 'Edit'
-		context: [
-			'message'
-			'message-mobile'
-		]
-		action: (e, instance) ->
-			message = $(e.currentTarget).closest('.message')[0]
-			chatMessages[Session.get('openedRoom')].edit(message)
-			RocketChat.MessageAction.hideDropDown()
-			input = instance.find('.input-message')
-			Meteor.setTimeout ->
-				input.focus()
-				input.updateAutogrow()
-			, 200
-		validation: (message) ->
-			if not RocketChat.models.Subscriptions.findOne({ rid: message.rid })?
-				return false
-
-			hasPermission = RocketChat.authz.hasAtLeastOnePermission('edit-message', message.rid)
-			isEditAllowed = RocketChat.settings.get 'Message_AllowEditing'
-			editOwn = message.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(message.ts) if message.ts?
-				currentTsDiff = moment().diff(msgTs, 'minutes') if msgTs?
-				return currentTsDiff < blockEditInMinutes
-			else
-				return true
-		order: 2
-
-	RocketChat.MessageAction.addButton
-		id: 'delete-message'
-		icon: 'icon-trash-alt'
-		i18nLabel: 'Delete'
-		context: [
-			'message'
-			'message-mobile'
-		]
-		action: (event, instance) ->
-			message = @_arguments[1]
-			RocketChat.MessageAction.hideDropDown()
-			chatMessages[Session.get('openedRoom')].confirmDeleteMsg(message)
-		validation: (message) ->
-			if not RocketChat.models.Subscriptions.findOne({ rid: message.rid })?
-				return false
-
-			hasPermission = RocketChat.authz.hasAtLeastOnePermission('delete-message', message.rid)
-			isDeleteAllowed = RocketChat.settings.get 'Message_AllowDeleting'
-			deleteOwn = message.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(message.ts) if message.ts?
-				currentTsDiff = moment().diff(msgTs, 'minutes') if msgTs?
-				return currentTsDiff < blockDeleteInMinutes
-			else
-				return true
-		order: 3
-
-	RocketChat.MessageAction.addButton
-		id: 'permalink'
-		icon: 'icon-link'
-		i18nLabel: 'Permalink'
-		classes: 'clipboard'
-		context: [
-			'message'
-			'message-mobile'
-		]
-		action: (event, instance) ->
-			message = @_arguments[1]
-			permalink = RocketChat.MessageAction.getPermaLink(message._id)
-			RocketChat.MessageAction.hideDropDown()
-			if Meteor.isCordova
-				cordova.plugins.clipboard.copy(permalink);
-			else
-				$(event.currentTarget).attr('data-clipboard-text', permalink);
-			toastr.success(TAPi18n.__('Copied'))
-		validation: (message) ->
-			if not RocketChat.models.Subscriptions.findOne({ rid: message.rid })?
-				return false
-
-			return true
-		order: 4
-
-	RocketChat.MessageAction.addButton
-		id: 'copy'
-		icon: 'icon-paste'
-		i18nLabel: 'Copy'
-		classes: 'clipboard'
-		context: [
-			'message'
-			'message-mobile'
-		]
-		action: (event, instance) ->
-			message = @_arguments[1].msg
-			RocketChat.MessageAction.hideDropDown()
-			if Meteor.isCordova
-				cordova.plugins.clipboard.copy(message);
-			else
-				$(event.currentTarget).attr('data-clipboard-text', message)
-			toastr.success(TAPi18n.__('Copied'))
-		validation: (message) ->
-			if not RocketChat.models.Subscriptions.findOne({ rid: message.rid })?
-				return false
-
-			return true
-		order: 5
-
-	RocketChat.MessageAction.addButton
-		id: 'quote-message'
-		icon: 'icon-quote-left'
-		i18nLabel: 'Quote'
-		context: [
-			'message'
-			'message-mobile'
-		]
-		action: (event, instance) ->
-			message = @_arguments[1]
-			input = instance.find('.input-message')
-			url = RocketChat.MessageAction.getPermaLink(message._id)
-			text = '[ ](' + url + ') '
-			if input.value
-				input.value += if input.value.endsWith(' ') then '' else ' '
-			input.value += text
-			input.focus()
-			RocketChat.MessageAction.hideDropDown()
-		validation: (message) ->
-			if not RocketChat.models.Subscriptions.findOne({ rid: message.rid })?
-				return false
-
-			return true
-		order: 6
diff --git a/packages/rocketchat-lib/client/MessageAction.js b/packages/rocketchat-lib/client/MessageAction.js
new file mode 100644
index 0000000000000000000000000000000000000000..51ad67e3fa5783e18d1109aa7cad96c3ba8d9aef
--- /dev/null
+++ b/packages/rocketchat-lib/client/MessageAction.js
@@ -0,0 +1,288 @@
+import moment from 'moment';
+
+import toastr from 'toastr';
+
+RocketChat.MessageAction = new class {
+	/*
+  	config expects the following keys (only id is mandatory):
+  		id (mandatory)
+  		icon: string
+  		i18nLabel: string
+  		action: function(event, instance)
+  		validation: function(message)
+  		order: integer
+   */
+
+	constructor() {
+		this.buttons = new ReactiveVar({});
+	}
+
+	addButton(config) {
+		if (!config || !config.id) {
+			return false;
+		}
+		return Tracker.nonreactive(() => {
+			const btns = this.buttons.get();
+			btns[config.id] = config;
+			return this.buttons.set(btns);
+		});
+	}
+
+	removeButton(id) {
+		return Tracker.nonreactive(() => {
+			const btns = this.buttons.get();
+			delete btns[id];
+			return this.buttons.set(btns);
+		});
+	}
+
+	updateButton(id, config) {
+		return Tracker.nonreactive(() => {
+			const btns = this.buttons.get();
+			if (btns[id]) {
+				btns[id] = _.extend(btns[id], config);
+				return this.buttons.set(btns);
+			}
+		});
+	}
+
+	getButtonById(id) {
+		const allButtons = this.buttons.get();
+		return allButtons[id];
+	}
+
+	getButtons(message, context) {
+		const allButtons = _.toArray(this.buttons.get());
+		let allowedButtons = allButtons;
+		if (message) {
+			allowedButtons = _.compact(_.map(allButtons, function(button) {
+				if (button.context == null || button.context.includes(context)) {
+					if (button.validation == null || button.validation(message, context)) {
+						return button;
+					}
+				}
+			}));
+		}
+		return _.sortBy(allowedButtons, 'order');
+	}
+
+	resetButtons() {
+		return this.buttons.set({});
+	}
+
+	getPermaLink(msgId) {
+		const roomData = ChatSubscription.findOne({
+			rid: Session.get('openedRoom')
+		});
+		let routePath = document.location.pathname;
+		if (roomData) {
+			routePath = RocketChat.roomTypes.getRouteLink(roomData.t, roomData);
+		}
+		return `${ Meteor.absoluteUrl().replace(/\/$/, '') + routePath }?msg=${ msgId }`;
+	}
+
+	hideDropDown() {
+		return $('.message-dropdown:visible').hide();
+	}
+};
+
+Meteor.startup(function() {
+	$(document).click((event) => {
+		const target = $(event.target);
+		if (!target.closest('.message-cog-container').length && !target.is('.message-cog-container')) {
+			return RocketChat.MessageAction.hideDropDown();
+		}
+	});
+
+	RocketChat.MessageAction.addButton({
+		id: 'reply-message',
+		icon: 'icon-reply',
+		i18nLabel: 'Reply',
+		context: ['message', 'message-mobile'],
+		action(event, instance) {
+			const message = this._arguments[1];
+			const input = instance.find('.input-message');
+			const url = RocketChat.MessageAction.getPermaLink(message._id);
+			const text = `[ ](${ url }) @${ message.u.username } `;
+			if (input.value) {
+				input.value += input.value.endsWith(' ') ? '' : ' ';
+			}
+			input.value += text;
+			input.focus();
+			return RocketChat.MessageAction.hideDropDown();
+		},
+		validation(message) {
+			if (RocketChat.models.Subscriptions.findOne({
+				rid: message.rid
+			}) == null) {
+				return false;
+			}
+			return true;
+		},
+		order: 1
+	});
+	/* globals chatMessages*/
+	RocketChat.MessageAction.addButton({
+		id: 'edit-message',
+		icon: 'icon-pencil',
+		i18nLabel: 'Edit',
+		context: ['message', 'message-mobile'],
+		action(e, instance) {
+			const message = $(e.currentTarget).closest('.message')[0];
+			chatMessages[Session.get('openedRoom')].edit(message);
+			RocketChat.MessageAction.hideDropDown();
+			const input = instance.find('.input-message');
+			Meteor.setTimeout(() => {
+				input.focus();
+				input.updateAutogrow();
+			}, 200);
+		},
+		validation(message) {
+			if (RocketChat.models.Subscriptions.findOne({
+				rid: message.rid
+			}) == null) {
+				return false;
+			}
+			const hasPermission = RocketChat.authz.hasAtLeastOnePermission('edit-message', message.rid);
+			const isEditAllowed = RocketChat.settings.get('Message_AllowEditing');
+			const editOwn = message.u && message.u._id === Meteor.userId();
+			if (!(hasPermission || (isEditAllowed && editOwn))) {
+				return;
+			}
+			const blockEditInMinutes = RocketChat.settings.get('Message_AllowEditing_BlockEditInMinutes');
+			if (blockEditInMinutes) {
+				let msgTs;
+				if (message.ts != null) {
+					msgTs = moment(message.ts);
+				}
+				let currentTsDiff;
+				if (msgTs != null) {
+					currentTsDiff = moment().diff(msgTs, 'minutes');
+				}
+				return currentTsDiff < blockEditInMinutes;
+			} else {
+				return true;
+			}
+		},
+		order: 2
+	});
+	RocketChat.MessageAction.addButton({
+		id: 'delete-message',
+		icon: 'icon-trash-alt',
+		i18nLabel: 'Delete',
+		context: ['message', 'message-mobile'],
+		action() {
+			const message = this._arguments[1];
+			RocketChat.MessageAction.hideDropDown();
+			return chatMessages[Session.get('openedRoom')].confirmDeleteMsg(message);
+		},
+		validation(message) {
+			if (RocketChat.models.Subscriptions.findOne({rid: message.rid}) == null) {
+				return false;
+			}
+			const forceDelete = RocketChat.authz.hasAtLeastOnePermission('force-delete-message', message.rid);
+			const hasPermission = RocketChat.authz.hasAtLeastOnePermission('delete-message', message.rid);
+			const isDeleteAllowed = RocketChat.settings.get('Message_AllowDeleting');
+			const deleteOwn = message.u && message.u._id === Meteor.userId();
+			if (!(hasPermission || (isDeleteAllowed && deleteOwn) || forceDelete)) {
+				return;
+			}
+			const blockDeleteInMinutes = RocketChat.settings.get('Message_AllowDeleting_BlockDeleteInMinutes');
+			if ((blockDeleteInMinutes != null) && blockDeleteInMinutes !== 0 && !(forceDelete)) {
+				let msgTs;
+				if (message.ts != null) {
+					msgTs = moment(message.ts);
+				}
+				let currentTsDiff;
+				if (msgTs != null) {
+					currentTsDiff = moment().diff(msgTs, 'minutes');
+				}
+				return currentTsDiff < blockDeleteInMinutes;
+			} else {
+				return true;
+			}
+		},
+		order: 3
+	});
+	/* globals cordova*/
+	RocketChat.MessageAction.addButton({
+		id: 'permalink',
+		icon: 'icon-link',
+		i18nLabel: 'Permalink',
+		classes: 'clipboard',
+		context: ['message', 'message-mobile'],
+		action() {
+			const message = this._arguments[1];
+			const permalink = RocketChat.MessageAction.getPermaLink(message._id);
+			RocketChat.MessageAction.hideDropDown();
+			if (Meteor.isCordova) {
+				cordova.plugins.clipboard.copy(permalink);
+			} else {
+				$(event.currentTarget).attr('data-clipboard-text', permalink);
+			}
+			return toastr.success(TAPi18n.__('Copied'));
+		},
+		validation(message) {
+			if (RocketChat.models.Subscriptions.findOne({
+				rid: message.rid
+			}) == null) {
+				return false;
+			}
+			return true;
+		},
+		order: 4
+	});
+	RocketChat.MessageAction.addButton({
+		id: 'copy',
+		icon: 'icon-paste',
+		i18nLabel: 'Copy',
+		classes: 'clipboard',
+		context: ['message', 'message-mobile'],
+		action() {
+			const message = this._arguments[1].msg;
+			RocketChat.MessageAction.hideDropDown();
+			if (Meteor.isCordova) {
+				cordova.plugins.clipboard.copy(message);
+			} else {
+				$(event.currentTarget).attr('data-clipboard-text', message);
+			}
+			return toastr.success(TAPi18n.__('Copied'));
+		},
+		validation(message) {
+			if (RocketChat.models.Subscriptions.findOne({
+				rid: message.rid
+			}) == null) {
+				return false;
+			}
+			return true;
+		},
+		order: 5
+	});
+	return RocketChat.MessageAction.addButton({
+		id: 'quote-message',
+		icon: 'icon-quote-left',
+		i18nLabel: 'Quote',
+		context: ['message', 'message-mobile'],
+		action(event, instance) {
+			const message = this._arguments[1];
+			const input = instance.find('.input-message');
+			const url = RocketChat.MessageAction.getPermaLink(message._id);
+			const text = `[ ](${ url }) `;
+			if (input.value) {
+				input.value += input.value.endsWith(' ') ? '' : ' ';
+			}
+			input.value += text;
+			input.focus();
+			return RocketChat.MessageAction.hideDropDown();
+		},
+		validation(message) {
+			if (RocketChat.models.Subscriptions.findOne({
+				rid: message.rid
+			}) == null) {
+				return false;
+			}
+			return true;
+		},
+		order: 6
+	});
+});
diff --git a/packages/rocketchat-lib/client/Notifications.coffee b/packages/rocketchat-lib/client/Notifications.coffee
deleted file mode 100644
index 9a51ceca34820ff0a5b1f665b42818616765fcf2..0000000000000000000000000000000000000000
--- a/packages/rocketchat-lib/client/Notifications.coffee
+++ /dev/null
@@ -1,72 +0,0 @@
-RocketChat.Notifications = new class
-	constructor: ->
-		@logged = Meteor.userId() isnt null
-		@loginCb = []
-		Tracker.autorun =>
-			if Meteor.userId() isnt null and this.logged is false
-				cb() for cb in this.loginCb
-
-			@logged = Meteor.userId() isnt null
-
-		@debug = false
-		@streamAll = new Meteor.Streamer 'notify-all'
-		@streamLogged = new Meteor.Streamer 'notify-logged'
-		@streamRoom = new Meteor.Streamer 'notify-room'
-		@streamRoomUsers = new Meteor.Streamer 'notify-room-users'
-		@streamUser = new Meteor.Streamer 'notify-user'
-
-		if @debug is true
-			@onAll -> console.log "RocketChat.Notifications: onAll", arguments
-			@onUser -> console.log "RocketChat.Notifications: onAll", arguments
-
-	onLogin: (cb) ->
-		@loginCb.push(cb)
-		if @logged
-			cb()
-
-	notifyRoom: (room, eventName, args...) ->
-		console.log "RocketChat.Notifications: notifyRoom", arguments if @debug is true
-
-		args.unshift "#{room}/#{eventName}"
-		@streamRoom.emit.apply @streamRoom, args
-
-	notifyUser: (userId, eventName, args...) ->
-		console.log "RocketChat.Notifications: notifyUser", arguments if @debug is true
-
-		args.unshift "#{userId}/#{eventName}"
-		@streamUser.emit.apply @streamUser, args
-
-	notifyUsersOfRoom: (room, eventName, args...) ->
-		console.log "RocketChat.Notifications: notifyUsersOfRoom", arguments if @debug is true
-
-		args.unshift "#{room}/#{eventName}"
-		@streamRoomUsers.emit.apply @streamRoomUsers, args
-
-	onAll: (eventName, callback) ->
-		@streamAll.on eventName, callback
-
-	onLogged: (eventName, callback) ->
-		@onLogin =>
-			@streamLogged.on eventName, callback
-
-	onRoom: (room, eventName, callback) ->
-		if @debug is true
-			@streamRoom.on room, -> console.log "RocketChat.Notifications: onRoom #{room}", arguments
-
-		@streamRoom.on "#{room}/#{eventName}", callback
-
-	onUser: (eventName, callback) ->
-		@streamUser.on "#{Meteor.userId()}/#{eventName}", callback
-
-
-	unAll: (callback) ->
-		@streamAll.removeListener 'notify', callback
-
-	unLogged: (callback) ->
-		@streamLogged.removeListener 'notify', callback
-
-	unRoom: (room, eventName, callback) ->
-		@streamRoom.removeListener "#{room}/#{eventName}", callback
-
-	unUser: (callback) ->
-		@streamUser.removeListener Meteor.userId(), callback
diff --git a/packages/rocketchat-lib/client/Notifications.js b/packages/rocketchat-lib/client/Notifications.js
new file mode 100644
index 0000000000000000000000000000000000000000..4a474c9aec24aeb1227e6d772ea46c595701e1c9
--- /dev/null
+++ b/packages/rocketchat-lib/client/Notifications.js
@@ -0,0 +1,86 @@
+RocketChat.Notifications = new class {
+	constructor() {
+		this.logged = Meteor.userId() !== null;
+		this.loginCb = [];
+		Tracker.autorun(() => {
+			if (Meteor.userId() !== null && this.logged === false) {
+				this.loginCb.forEach(cb => cb());
+			}
+			return this.logged = Meteor.userId() !== null;
+		});
+		this.debug = false;
+		this.streamAll = new Meteor.Streamer('notify-all');
+		this.streamLogged = new Meteor.Streamer('notify-logged');
+		this.streamRoom = new Meteor.Streamer('notify-room');
+		this.streamRoomUsers = new Meteor.Streamer('notify-room-users');
+		this.streamUser = new Meteor.Streamer('notify-user');
+		if (this.debug === true) {
+			this.onAll(function() {
+				return console.log('RocketChat.Notifications: onAll', arguments);
+			});
+			this.onUser(function() {
+				return console.log('RocketChat.Notifications: onAll', arguments);
+			});
+		}
+	}
+
+	onLogin(cb) {
+		this.loginCb.push(cb);
+		if (this.logged) {
+			return cb();
+		}
+	}
+	notifyRoom(room, eventName, ...args) {
+		if (this.debug === true) {
+			console.log('RocketChat.Notifications: notifyRoom', arguments);
+		}
+		args.unshift(`${ room }/${ eventName }`);
+		return this.streamRoom.emit.apply(this.streamRoom, args);
+	}
+	notifyUser(userId, eventName, ...args) {
+		if (this.debug === true) {
+			console.log('RocketChat.Notifications: notifyUser', arguments);
+		}
+		args.unshift(`${ userId }/${ eventName }`);
+		return this.streamUser.emit.apply(this.streamUser, args);
+	}
+	notifyUsersOfRoom(room, eventName, ...args) {
+		if (this.debug === true) {
+			console.log('RocketChat.Notifications: notifyUsersOfRoom', arguments);
+		}
+		args.unshift(`${ room }/${ eventName }`);
+		return this.streamRoomUsers.emit.apply(this.streamRoomUsers, args);
+	}
+	onAll(eventName, callback) {
+		return this.streamAll.on(eventName, callback);
+	}
+	onLogged(eventName, callback) {
+		return this.onLogin(() => {
+			return this.streamLogged.on(eventName, callback);
+		});
+	}
+	onRoom(room, eventName, callback) {
+		if (this.debug === true) {
+			this.streamRoom.on(room, function() {
+				return console.log(`RocketChat.Notifications: onRoom ${ room }`, arguments);
+			});
+		}
+		return this.streamRoom.on(`${ room }/${ eventName }`, callback);
+	}
+	onUser(eventName, callback) {
+		return this.streamUser.on(`${ Meteor.userId() }/${ eventName }`, callback);
+	}
+	unAll(callback) {
+		return this.streamAll.removeListener('notify', callback);
+	}
+	unLogged(callback) {
+		return this.streamLogged.removeListener('notify', callback);
+	}
+	unRoom(room, eventName, callback) {
+		return this.streamRoom.removeListener(`${ room }/${ eventName }`, callback);
+	}
+	unUser(callback) {
+		return this.streamUser.removeListener(Meteor.userId(), callback);
+	}
+
+};
diff --git a/packages/rocketchat-lib/client/lib/openRoom.coffee b/packages/rocketchat-lib/client/lib/openRoom.coffee
index 33e023abf9c97482f2c8a36f7eff1c08522e03d0..3130cd82a3e0f41ead8b19fd979e576125d94623 100644
--- a/packages/rocketchat-lib/client/lib/openRoom.coffee
+++ b/packages/rocketchat-lib/client/lib/openRoom.coffee
@@ -6,7 +6,7 @@ currentTracker = undefined
 	Meteor.defer ->
 		currentTracker = Tracker.autorun (c) ->
 			user = Meteor.user()
-			if (user? and not user.username?) or (not user? and RocketChat.settings.get('Accounts_AllowAnonymousAccess') is false)
+			if (user? and not user.username?) or (not user? and RocketChat.settings.get('Accounts_AllowAnonymousRead') is false)
 				BlazeLayout.render 'main'
 				return
 
diff --git a/packages/rocketchat-lib/client/lib/openRoom.js b/packages/rocketchat-lib/client/lib/openRoom.js
new file mode 100644
index 0000000000000000000000000000000000000000..b283b51c08abd988c5f96c17c044bd6052e51f64
--- /dev/null
+++ b/packages/rocketchat-lib/client/lib/openRoom.js
@@ -0,0 +1,98 @@
+/* globals fireGlobalEvent readMessage currentTracker*/
+currentTracker = undefined;
+
+function openRoom(type, name) {
+	Session.set('openedRoom', null);
+
+	return Meteor.defer(() =>
+		currentTracker = Tracker.autorun(function(c) {
+			const user = Meteor.user();
+			if ((user && user.username == null) || user == null && RocketChat.settings.get('Accounts_AllowAnonymousAccess') === false) {
+				BlazeLayout.render('main');
+				return;
+			}
+
+			if (RoomManager.open(type + name).ready() !== true) {
+				BlazeLayout.render('main', { modal: RocketChat.Layout.isEmbedded(), center: 'loading' });
+				return;
+			}
+			if (currentTracker) {
+				currentTracker = undefined;
+			}
+			c.stop();
+
+			const room = RocketChat.roomTypes.findRoom(type, name, user);
+			if (room == null) {
+				if (type === 'd') {
+					Meteor.call('createDirectMessage', name, function(err) {
+						if (!err) {
+							RoomManager.close(type + name);
+							return openRoom('d', name);
+						} else {
+							Session.set('roomNotFound', {type, name});
+							BlazeLayout.render('main', {center: 'roomNotFound'});
+							return;
+						}
+					});
+				} else {
+					Meteor.call('getRoomByTypeAndName', type, name, function(err, record) {
+						if (err) {
+							Session.set('roomNotFound', {type, name});
+							return BlazeLayout.render('main', {center: 'roomNotFound'});
+						} else {
+							delete record.$loki;
+							RocketChat.models.Rooms.upsert({ _id: record._id }, _.omit(record, '_id'));
+							RoomManager.close(type + name);
+							return openRoom(type, name);
+						}
+					});
+				}
+				return;
+			}
+
+			const mainNode = document.querySelector('.main-content');
+			if (mainNode) {
+				for (const child of Array.from(mainNode.children)) {
+					if (child) { mainNode.removeChild(child); }
+				}
+				const roomDom = RoomManager.getDomOfRoom(type + name, room._id);
+				mainNode.appendChild(roomDom);
+				if (roomDom.classList.contains('room-container')) {
+					roomDom.querySelector('.messages-box > .wrapper').scrollTop = roomDom.oldScrollTop;
+				}
+			}
+
+			Session.set('openedRoom', room._id);
+
+			fireGlobalEvent('room-opened', _.omit(room, 'usernames'));
+
+			Session.set('editRoomTitle', false);
+			RoomManager.updateMentionsMarksOfRoom(type + name);
+			Meteor.setTimeout(() => readMessage.readNow(), 2000);
+			// KonchatNotification.removeRoomNotification(params._id)
+
+			if (Meteor.Device.isDesktop() && window.chatMessages && window.chatMessages[room._id] != null) {
+				setTimeout(() => $('.message-form .input-message').focus(), 100);
+			}
+
+			// update user's room subscription
+			const sub = ChatSubscription.findOne({rid: room._id});
+			if (sub && sub.open === false) {
+				Meteor.call('openRoom', room._id, function(err) {
+					if (err) {
+						return handleError(err);
+					}
+				});
+			}
+
+			if (FlowRouter.getQueryParam('msg')) {
+				const msg = { _id: FlowRouter.getQueryParam('msg'), rid: room._id };
+				RoomHistoryManager.getSurroundingMessages(msg);
+			}
+
+			return RocketChat.callbacks.run('enter-room', sub);
+		})
+	);
+}
+export { openRoom };
+this.openRoom = openRoom;
diff --git a/packages/rocketchat-lib/client/lib/roomExit.coffee b/packages/rocketchat-lib/client/lib/roomExit.coffee
deleted file mode 100644
index d7da9ed4a8718746e4b3c507e468a6411e9d59b6..0000000000000000000000000000000000000000
--- a/packages/rocketchat-lib/client/lib/roomExit.coffee
+++ /dev/null
@@ -1,20 +0,0 @@
-@roomExit = ->
-	RocketChat.callbacks.run 'roomExit'
-
-	BlazeLayout.render 'main', {center: 'none'}
-
-	if currentTracker?
-		currentTracker.stop()
-
-	mainNode = document.querySelector('.main-content')
-	if mainNode?
-		for child in mainNode.children
-			if child?
-				if child.classList.contains('room-container')
-					wrapper = child.querySelector('.messages-box > .wrapper')
-					if wrapper
-						if wrapper.scrollTop >= wrapper.scrollHeight - wrapper.clientHeight
-							child.oldScrollTop = 10e10
-						else
-							child.oldScrollTop = wrapper.scrollTop
-				mainNode.removeChild child
diff --git a/packages/rocketchat-lib/client/lib/roomExit.js b/packages/rocketchat-lib/client/lib/roomExit.js
new file mode 100644
index 0000000000000000000000000000000000000000..36f1260e34207bac72a5846602290a583f13b399
--- /dev/null
+++ b/packages/rocketchat-lib/client/lib/roomExit.js
@@ -0,0 +1,31 @@
+/*globals currentTracker */
+this.roomExit = function() {
+	RocketChat.callbacks.run('roomExit');
+	BlazeLayout.render('main', {
+		center: 'none'
+	});
+
+	if (typeof currentTracker !== 'undefined') {
+		currentTracker.stop();
+	}
+	const mainNode = document.querySelector('.main-content');
+	if (mainNode == null) {
+		return;
+	}
+	return [...mainNode.children].forEach(child => {
+		if (child == null) {
+			return;
+		}
+		if (child.classList.contains('room-container')) {
+			const wrapper = child.querySelector('.messages-box > .wrapper');
+			if (wrapper) {
+				if (wrapper.scrollTop >= wrapper.scrollHeight - wrapper.clientHeight) {
+					child.oldScrollTop = 10e10;
+				} else {
+					child.oldScrollTop = wrapper.scrollTop;
+				}
+			}
+		}
+		mainNode.removeChild(child);
+	});
+};
diff --git a/packages/rocketchat-lib/client/lib/roomTypes.coffee b/packages/rocketchat-lib/client/lib/roomTypes.coffee
deleted file mode 100644
index afe19195bf91384b4a1baa13ee112cc350708116..0000000000000000000000000000000000000000
--- a/packages/rocketchat-lib/client/lib/roomTypes.coffee
+++ /dev/null
@@ -1,90 +0,0 @@
-RocketChat.roomTypes = new class roomTypesClient extends roomTypesCommon
-	checkCondition: (roomType) ->
-		return not roomType.condition? or roomType.condition()
-
-	getTypes: ->
-		orderedTypes = []
-
-		_.sortBy(@roomTypesOrder, 'order').forEach (type) =>
-			orderedTypes.push @roomTypes[type.identifier]
-
-		return orderedTypes
-
-	getIcon: (roomType) ->
-		return @roomTypes[roomType]?.icon
-
-	getRoomName: (roomType, roomData) ->
-		return @roomTypes[roomType]?.roomName roomData
-
-	getSecondaryRoomName: (roomType, roomData) ->
-		return @roomTypes[roomType]?.secondaryRoomName?(roomData)
-
-	getIdentifiers: (except) ->
-		except = [].concat except
-		list = _.reject @roomTypesOrder, (t) -> return except.indexOf(t.identifier) isnt -1
-		return _.map list, (t) -> return t.identifier
-
-	getUserStatus: (roomType, roomId) ->
-		return @roomTypes[roomType]?.getUserStatus?(roomId)
-
-	findRoom: (roomType, identifier, user) ->
-		return @roomTypes[roomType]?.findRoom identifier, user
-
-	canSendMessage: (roomId) ->
-		return ChatSubscription.find({ rid: roomId }).count() > 0
-
-	readOnly: (roomId, user) ->
-
-		fields = { ro: 1 }
-
-		# if a user has been specified then we want to see if that user has been muted in the room
-		if user
-			fields.muted = 1
-
-		room = ChatRoom.findOne({ _id: roomId }, fields : fields)
-
-		unless user
-			return room?.ro;
-
-		userOwner = RoomRoles.findOne({ rid: roomId, "u._id": user._id, roles: 'owner' }, { fields: { _id: 1 } })
-
-		return room?.ro is true and Array.isArray(room?.muted) and room?.muted.indexOf(user.username) != -1 and !userOwner
-
-	archived: (roomId) ->
-		fields = { archived: 1 }
-
-		room = ChatRoom.findOne({ _id: roomId }, fields : fields)
-
-		return room?.archived is true
-
-	verifyCanSendMessage: (roomId) ->
-		room = ChatRoom.findOne({ _id: roomId }, { fields: { t: 1 } })
-		return if not room?.t?
-
-		roomType = room.t
-
-		return @roomTypes[roomType]?.canSendMessage roomId if @roomTypes[roomType]?.canSendMessage?
-
-		return @canSendMessage roomId
-
-	verifyShowJoinLink: (roomId) ->
-		room = ChatRoom.findOne({ _id: roomId }, { fields: { t: 1 } })
-		return if not room?.t?
-
-		roomType = room.t
-
-		if not @roomTypes[roomType]?.showJoinLink?
-			return false
-
-		return @roomTypes[roomType].showJoinLink roomId
-
-	getNotSubscribedTpl: (roomId) ->
-		room = ChatRoom.findOne({ _id: roomId }, { fields: { t: 1 } })
-		return if not room?.t?
-
-		roomType = room.t
-
-		if not @roomTypes[roomType]?.notSubscribedTpl?
-			return false
-
-		return @roomTypes[roomType].notSubscribedTpl
diff --git a/packages/rocketchat-lib/client/lib/roomTypes.js b/packages/rocketchat-lib/client/lib/roomTypes.js
new file mode 100644
index 0000000000000000000000000000000000000000..fdc926c063da070bddc26e0ed9bd95dbcfb640b8
--- /dev/null
+++ b/packages/rocketchat-lib/client/lib/roomTypes.js
@@ -0,0 +1,115 @@
+import roomTypesCommon from '../../lib/roomTypesCommon';
+
+RocketChat.roomTypes = new class extends roomTypesCommon {
+	checkCondition(roomType) {
+		return roomType.condition == null || roomType.condition();
+	}
+	getTypes() {
+		return _.sortBy(this.roomTypesOrder, 'order').map((type) => this.roomTypes[type.identifier]);
+	}
+	getIcon(roomType) {
+		return this.roomTypes[roomType] && this.roomTypes[roomType].icon;
+	}
+	getRoomName(roomType, roomData) {
+		return this.roomTypes[roomType] && this.roomTypes[roomType].roomName && this.roomTypes[roomType].roomName(roomData);
+	}
+	getSecondaryRoomName(roomType, roomData) {
+		return this.roomTypes[roomType] && typeof this.roomTypes[roomType].secondaryRoomName === 'function' && this.roomTypes[roomType].secondaryRoomName(roomData);
+	}
+	getIdentifiers(e) {
+		const except = [].concat(e);
+		const list = _.reject(this.roomTypesOrder, (t) => except.indexOf(t.identifier) !== -1);
+		return _.map(list, (t) => t.identifier);
+	}
+	getUserStatus(roomType, 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);
+	}
+	canSendMessage(roomId) {
+		return ChatSubscription.find({
+			rid: roomId
+		}).count() > 0;
+	}
+	readOnly(roomId, user) {
+		const fields = {
+			ro: 1
+		};
+		if (user) {
+			fields.muted = 1;
+		}
+		const room = ChatRoom.findOne({
+			_id: roomId
+		}, {
+			fields
+		});
+		if (!user) {
+			return room && room.ro;
+		}
+		/* globals RoomRoles */
+		const userOwner = RoomRoles.findOne({
+			rid: roomId,
+			'u._id': user._id,
+			roles: 'owner'
+		}, {
+			fields: {
+				_id: 1
+			}
+		});
+		return room && (room.ro === true && Array.isArray(room.muted) && room.muted.indexOf(user.username) !== -1 && !userOwner);
+	}
+	archived(roomId) {
+		const fields = {
+			archived: 1
+		};
+		const room = ChatRoom.findOne({
+			_id: roomId
+		}, {
+			fields
+		});
+		return room && room.archived === true;
+	}
+	verifyCanSendMessage(roomId) {
+		const room = ChatRoom.findOne({	_id: roomId }, { fields: { t: 1 }});
+
+		if (!room || !room.t) {
+			return;
+		}
+
+		const roomType = room.t;
+		if (this.roomTypes[roomType] && this.roomTypes[roomType].canSendMessage) {
+			return this.roomTypes[roomType].canSendMessage(roomId);
+		}
+		return this.canSendMessage(roomId);
+	}
+	verifyShowJoinLink(roomId) {
+		const room = ChatRoom.findOne({
+			_id: roomId
+		}, {
+			fields: {
+				t: 1
+			}
+		});
+		if (!room || !room.t) {
+			return;
+		}
+		const roomType = room.t;
+		if (this.roomTypes[roomType] && !this.roomTypes[roomType].showJoinLink) {
+			return false;
+		}
+		return this.roomTypes[roomType].showJoinLink(roomId);
+	}
+	getNotSubscribedTpl(roomId) {
+		const room = ChatRoom.findOne({ _id: roomId }, { fields: { t: 1 }});
+		if (!room || !room.t) {
+			return;
+		}
+		const roomType = room.t;
+		if (this.roomTypes[roomType] && !this.roomTypes[roomType].notSubscribedTpl) {
+			return false;
+		}
+		return this.roomTypes[roomType].notSubscribedTpl;
+	}
+
+};
diff --git a/packages/rocketchat-lib/client/lib/settings.coffee b/packages/rocketchat-lib/client/lib/settings.coffee
deleted file mode 100644
index 38c08e546ba265db1bea2ae476ef3c1188dd76c5..0000000000000000000000000000000000000000
--- a/packages/rocketchat-lib/client/lib/settings.coffee
+++ /dev/null
@@ -1,65 +0,0 @@
-###
-# RocketChat.settings holds all packages settings
-# @namespace RocketChat.settings
-###
-
-RocketChat.settings.cachedCollection = new RocketChat.CachedCollection({ name: 'public-settings', eventType: 'onAll', userRelated: false })
-RocketChat.settings.collection = RocketChat.settings.cachedCollection.collection
-RocketChat.settings.cachedCollection.init()
-
-RocketChat.settings.dict = new ReactiveDict 'settings'
-
-RocketChat.settings.get = (_id) ->
-	return RocketChat.settings.dict.get(_id)
-
-RocketChat.settings.init = ->
-	initialLoad = true
-	RocketChat.settings.collection.find().observe
-		added: (record) ->
-			Meteor.settings[record._id] = record.value
-			RocketChat.settings.dict.set record._id, record.value
-			RocketChat.settings.load record._id, record.value, initialLoad
-		changed: (record) ->
-			Meteor.settings[record._id] = record.value
-			RocketChat.settings.dict.set record._id, record.value
-			RocketChat.settings.load record._id, record.value, initialLoad
-		removed: (record) ->
-			delete Meteor.settings[record._id]
-			RocketChat.settings.dict.set record._id, undefined
-			RocketChat.settings.load record._id, undefined, initialLoad
-	initialLoad = false
-
-RocketChat.settings.init()
-
-Meteor.startup ->
-	if Meteor.isCordova is false
-		Tracker.autorun (c) ->
-			siteUrl = RocketChat.settings.get('Site_Url')
-			if not siteUrl or not Meteor.userId()?
-				return
-
-			if RocketChat.authz.hasRole(Meteor.userId(), 'admin') is false or Meteor.settings.public.sandstorm
-				return c.stop()
-
-			Meteor.setTimeout ->
-				if __meteor_runtime_config__.ROOT_URL isnt location.origin
-					currentUrl = location.origin + __meteor_runtime_config__.ROOT_URL_PATH_PREFIX
-					swal
-						type: 'warning'
-						title: t('Warning')
-						text: t("The_setting_s_is_configured_to_s_and_you_are_accessing_from_s", t('Site_Url'), siteUrl, currentUrl) + '<br/><br/>' + t("Do_you_want_to_change_to_s_question", currentUrl)
-						showCancelButton: true
-						confirmButtonText: t('Yes')
-						cancelButtonText: t('Cancel')
-						closeOnConfirm: false
-						html: true
-					, ->
-						Meteor.call 'saveSetting', 'Site_Url', currentUrl, ->
-							swal
-								title: t('Saved')
-								type: 'success'
-								timer: 1000
-								showConfirmButton: false
-			, 100
-
-			return c.stop()
diff --git a/packages/rocketchat-lib/client/lib/settings.js b/packages/rocketchat-lib/client/lib/settings.js
new file mode 100644
index 0000000000000000000000000000000000000000..0f0c946ecd81f4250bae1b0358b165754150f9a8
--- /dev/null
+++ b/packages/rocketchat-lib/client/lib/settings.js
@@ -0,0 +1,87 @@
+
+/*
+* RocketChat.settings holds all packages settings
+* @namespace RocketChat.settings
+*/
+
+/* globals ReactiveDict*/
+
+RocketChat.settings.cachedCollection = new RocketChat.CachedCollection({
+	name: 'public-settings',
+	eventType: 'onAll',
+	userRelated: false
+});
+
+RocketChat.settings.collection = RocketChat.settings.cachedCollection.collection;
+
+RocketChat.settings.cachedCollection.init();
+
+RocketChat.settings.dict = new ReactiveDict('settings');
+
+RocketChat.settings.get = function(_id) {
+	return RocketChat.settings.dict.get(_id);
+};
+
+RocketChat.settings.init = function() {
+	let initialLoad = true;
+	RocketChat.settings.collection.find().observe({
+		added(record) {
+			Meteor.settings[record._id] = record.value;
+			RocketChat.settings.dict.set(record._id, record.value);
+			return RocketChat.settings.load(record._id, record.value, initialLoad);
+		},
+		changed(record) {
+			Meteor.settings[record._id] = record.value;
+			RocketChat.settings.dict.set(record._id, record.value);
+			return RocketChat.settings.load(record._id, record.value, initialLoad);
+		},
+		removed(record) {
+			delete Meteor.settings[record._id];
+			RocketChat.settings.dict.set(record._id, null);
+			return RocketChat.settings.load(record._id, null, initialLoad);
+		}
+	});
+	return initialLoad = false;
+};
+
+RocketChat.settings.init();
+
+Meteor.startup(function() {
+	if (Meteor.isCordova === true) {
+		return;
+	}
+	Tracker.autorun(function(c) {
+		const siteUrl = RocketChat.settings.get('Site_Url');
+		if (!siteUrl || (Meteor.userId() == null)) {
+			return;
+		}
+		if (RocketChat.authz.hasRole(Meteor.userId(), 'admin') === false || Meteor.settings['public'].sandstorm) {
+			return c.stop();
+		}
+		Meteor.setTimeout(function() {
+			if (__meteor_runtime_config__.ROOT_URL !== location.origin) {
+				const currentUrl = location.origin + __meteor_runtime_config__.ROOT_URL_PATH_PREFIX;
+				swal({
+					type: 'warning',
+					title: t('Warning'),
+					text: `${ t('The_setting_s_is_configured_to_s_and_you_are_accessing_from_s', t('Site_Url'), siteUrl, currentUrl) }<br/><br/>${ t('Do_you_want_to_change_to_s_question', currentUrl) }`,
+					showCancelButton: true,
+					confirmButtonText: t('Yes'),
+					cancelButtonText: t('Cancel'),
+					closeOnConfirm: false,
+					html: true
+				}, function() {
+					Meteor.call('saveSetting', 'Site_Url', currentUrl, function() {
+						swal({
+							title: t('Saved'),
+							type: 'success',
+							timer: 1000,
+							showConfirmButton: false
+						});
+					});
+				});
+			}
+		}, 100);
+		return c.stop();
+	});
+});
diff --git a/packages/rocketchat-lib/client/methods/sendMessage.coffee b/packages/rocketchat-lib/client/methods/sendMessage.coffee
deleted file mode 100644
index 68cb0a2f8c6ef0fc27ec54c4363be222dc72bed2..0000000000000000000000000000000000000000
--- a/packages/rocketchat-lib/client/methods/sendMessage.coffee
+++ /dev/null
@@ -1,26 +0,0 @@
-Meteor.methods
-	sendMessage: (message) ->
-		if not Meteor.userId()
-			return false
-
-		if _.trim(message.msg) isnt ''
-			if isNaN(TimeSync.serverOffset())
-				message.ts = new Date()
-			else
-				message.ts = new Date(Date.now() + TimeSync.serverOffset())
-
-			user = Meteor.user()
-			message.u =
-				_id: Meteor.userId()
-				username: user.username
-
-			if RocketChat.settings.get('UI_Use_Real_Name')
-				message.u.name = user.name
-
-			message.temp = true
-
-			message = RocketChat.callbacks.run 'beforeSaveMessage', message
-
-			RocketChat.promises.run('onClientMessageReceived', message).then (message) ->
-				ChatMessage.insert message
-				RocketChat.callbacks.run 'afterSaveMessage', message
diff --git a/packages/rocketchat-lib/client/methods/sendMessage.js b/packages/rocketchat-lib/client/methods/sendMessage.js
new file mode 100644
index 0000000000000000000000000000000000000000..85cce415c2a88907602509fd4196458200ff1586
--- /dev/null
+++ b/packages/rocketchat-lib/client/methods/sendMessage.js
@@ -0,0 +1,22 @@
+Meteor.methods({
+	sendMessage(message) {
+		if (!Meteor.userId() || _.trim(message.msg) === '') {
+			return false;
+		}
+		const user = Meteor.user();
+		message.ts = isNaN(TimeSync.serverOffset()) ? new Date() : new Date(Date.now() + TimeSync.serverOffset());
+		message.u = {
+			_id: Meteor.userId(),
+			username: user.username
+		};
+		if (RocketChat.settings.get('UI_Use_Real_Name')) {
+			message.u.name = user.name;
+		}
+		message.temp = true;
+		message = RocketChat.callbacks.run('beforeSaveMessage', message);
+		RocketChat.promises.run('onClientMessageReceived', message).then(function(message) {
+			ChatMessage.insert(message);
+			return RocketChat.callbacks.run('afterSaveMessage', message);
+		});
+	}
+});
diff --git a/packages/rocketchat-lib/client/models/Uploads.coffee b/packages/rocketchat-lib/client/models/Uploads.coffee
deleted file mode 100644
index 4572e735b99b606c4227850370eeca118331bf0b..0000000000000000000000000000000000000000
--- a/packages/rocketchat-lib/client/models/Uploads.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-RocketChat.models.Uploads = new class extends RocketChat.models._Base
-	constructor: ->
-		@_initModel 'uploads'
diff --git a/packages/rocketchat-lib/client/models/Uploads.js b/packages/rocketchat-lib/client/models/Uploads.js
new file mode 100644
index 0000000000000000000000000000000000000000..eefe630c708c45d0ea928bc463c709d57ebb185f
--- /dev/null
+++ b/packages/rocketchat-lib/client/models/Uploads.js
@@ -0,0 +1,7 @@
+
+RocketChat.models.Uploads = new class extends RocketChat.models._Base {
+	constructor() {
+		super();
+		this._initModel('uploads');
+	}
+};
diff --git a/packages/rocketchat-lib/client/models/_Base.coffee b/packages/rocketchat-lib/client/models/_Base.coffee
deleted file mode 100644
index fb485723241a050a72683b29197e66489c222a55..0000000000000000000000000000000000000000
--- a/packages/rocketchat-lib/client/models/_Base.coffee
+++ /dev/null
@@ -1,44 +0,0 @@
-RocketChat.models._Base = class
-	_baseName: ->
-		return 'rocketchat_'
-
-	_initModel: (name) ->
-		check name, String
-
-		@model = new Mongo.Collection @_baseName() + name
-
-	find: ->
-		return @model.find.apply @model, arguments
-
-	findOne: ->
-		return @model.findOne.apply @model, arguments
-
-	insert: ->
-		return @model.insert.apply @model, arguments
-
-	update: ->
-		return @model.update.apply @model, arguments
-
-	upsert: ->
-		return @model.upsert.apply @model, arguments
-
-	remove: ->
-		return @model.remove.apply @model, arguments
-
-	allow: ->
-		return @model.allow.apply @model, arguments
-
-	deny: ->
-		return @model.deny.apply @model, arguments
-
-	ensureIndex: ->
-		return
-
-	dropIndex: ->
-		return
-
-	tryEnsureIndex: ->
-		return
-
-	tryDropIndex: ->
-		return
diff --git a/packages/rocketchat-lib/client/models/_Base.js b/packages/rocketchat-lib/client/models/_Base.js
new file mode 100644
index 0000000000000000000000000000000000000000..55623317be663df437f1d3e3d17191a97a47ddf8
--- /dev/null
+++ b/packages/rocketchat-lib/client/models/_Base.js
@@ -0,0 +1,52 @@
+RocketChat.models._Base = class {
+
+	_baseName() {
+		return 'rocketchat_';
+	}
+
+	_initModel(name) {
+		check(name, String);
+		return this.model = new Mongo.Collection(this._baseName() + name);
+	}
+
+	find() {
+		return this.model.find.apply(this.model, arguments);
+	}
+
+	findOne() {
+		return this.model.findOne.apply(this.model, arguments);
+	}
+
+	insert() {
+		return this.model.insert.apply(this.model, arguments);
+	}
+
+	update() {
+		return this.model.update.apply(this.model, arguments);
+	}
+
+	upsert() {
+		return this.model.upsert.apply(this.model, arguments);
+	}
+
+	remove() {
+		return this.model.remove.apply(this.model, arguments);
+	}
+
+	allow() {
+		return this.model.allow.apply(this.model, arguments);
+	}
+
+	deny() {
+		return this.model.deny.apply(this.model, arguments);
+	}
+
+	ensureIndex() {}
+
+	dropIndex() {}
+
+	tryEnsureIndex() {}
+
+	tryDropIndex() {}
+
+};
diff --git a/packages/rocketchat-lib/lib/callbacks.coffee b/packages/rocketchat-lib/lib/callbacks.coffee
deleted file mode 100644
index d0e3feb8ff32ee535f05d92f7e70be9533dd498e..0000000000000000000000000000000000000000
--- a/packages/rocketchat-lib/lib/callbacks.coffee
+++ /dev/null
@@ -1,129 +0,0 @@
-# https://github.com/TelescopeJS/Telescope/blob/master/packages/telescope-lib/lib/callbacks.js
-
-###
-# Callback hooks provide an easy way to add extra steps to common operations.
-# @namespace RocketChat.callbacks
-###
-RocketChat.callbacks = {}
-
-if Meteor.isServer
-	RocketChat.callbacks.showTime = true
-	RocketChat.callbacks.showTotalTime = true
-else
-	RocketChat.callbacks.showTime = false
-	RocketChat.callbacks.showTotalTime = false
-
-###
-# Callback priorities
-###
-RocketChat.callbacks.priority =
-	HIGH: -1000
-	MEDIUM: 0
-	LOW: 1000
-
-###
-# Add a callback function to a hook
-# @param {String} hook - The name of the hook
-# @param {Function} callback - The callback function
-###
-RocketChat.callbacks.add = (hook, callback, priority, id) ->
-	# if callback array doesn't exist yet, initialize it
-	priority ?= RocketChat.callbacks.priority.MEDIUM
-	unless _.isNumber priority
-		priority = RocketChat.callbacks.priority.MEDIUM
-	callback.priority = priority
-	callback.id = id or Random.id()
-	RocketChat.callbacks[hook] ?= []
-
-	if RocketChat.callbacks.showTime is true
-		err = new Error
-		callback.stack = err.stack
-
-		# if not id?
-		# 	console.log('Callback without id', callback.stack)
-
-	# Avoid adding the same callback twice
-	for cb in RocketChat.callbacks[hook]
-		if cb.id is callback.id
-			return
-
-	RocketChat.callbacks[hook].push callback
-	return
-
-###
-# Remove a callback from a hook
-# @param {string} hook - The name of the hook
-# @param {string} id - The callback's id
-###
-
-RocketChat.callbacks.remove = (hookName, id) ->
-	RocketChat.callbacks[hookName] = _.reject RocketChat.callbacks[hookName], (callback) ->
-		callback.id is id
-	return
-
-###
-# Successively run all of a hook's callbacks on an item
-# @param {String} hook - The name of the hook
-# @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks
-# @param {Object} [constant] - An optional constant that will be passed along to each callback
-# @returns {Object} Returns the item after it's been through all the callbacks for this hook
-###
-
-RocketChat.callbacks.run = (hook, item, constant) ->
-	callbacks = RocketChat.callbacks[hook]
-	if !!callbacks?.length
-		if RocketChat.callbacks.showTotalTime is true
-			totalTime = 0
-
-		# if the hook exists, and contains callbacks to run
-		result = _.sortBy(callbacks, (callback) -> return callback.priority or RocketChat.callbacks.priority.MEDIUM).reduce (result, callback) ->
-			# console.log(callback.name);
-			if RocketChat.callbacks.showTime is true or RocketChat.callbacks.showTotalTime is true
-				time = Date.now()
-
-			callbackResult = callback result, constant
-
-			if RocketChat.callbacks.showTime is true or RocketChat.callbacks.showTotalTime is true
-				currentTime = Date.now() - time
-				totalTime += currentTime
-				if RocketChat.callbacks.showTime is true
-					if Meteor.isServer
-						RocketChat.statsTracker.timing('callbacks.time', currentTime, ["hook:#{hook}", "callback:#{callback.id}"]);
-					else
-						console.log String(currentTime), hook, callback.id, callback.stack?.split?('\n')[2]?.match(/\(.+\)/)?[0]
-
-			return if typeof callbackResult == 'undefined' then result else callbackResult
-		, item
-
-		if RocketChat.callbacks.showTotalTime is true
-			if Meteor.isServer
-				RocketChat.statsTracker.timing('callbacks.totalTime', totalTime, ["hook:#{hook}"]);
-			else
-				console.log hook+':', totalTime
-
-		return result
-	else
-		# else, just return the item unchanged
-		return item
-
-###
-# Successively run all of a hook's callbacks on an item, in async mode (only works on server)
-# @param {String} hook - The name of the hook
-# @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks
-# @param {Object} [constant] - An optional constant that will be passed along to each callback
-###
-
-RocketChat.callbacks.runAsync = (hook, item, constant) ->
-	callbacks = RocketChat.callbacks[hook]
-	if Meteor.isServer and !!callbacks?.length
-		# use defer to avoid holding up client
-		Meteor.defer ->
-			# run all post submit server callbacks on post object successively
-			_.sortBy(callbacks, (callback) -> return callback.priority or RocketChat.callbacks.priority.MEDIUM).forEach (callback) ->
-				# console.log(callback.name);
-				callback item, constant
-				return
-			return
-	else
-		return item
-	return
diff --git a/packages/rocketchat-lib/lib/callbacks.js b/packages/rocketchat-lib/lib/callbacks.js
new file mode 100644
index 0000000000000000000000000000000000000000..86f56a2eea8aef5208a883edcfb7ccbffbb9651a
--- /dev/null
+++ b/packages/rocketchat-lib/lib/callbacks.js
@@ -0,0 +1,131 @@
+/*
+* Callback hooks provide an easy way to add extra steps to common operations.
+* @namespace RocketChat.callbacks
+*/
+
+RocketChat.callbacks = {};
+
+if (Meteor.isServer) {
+	RocketChat.callbacks.showTime = true;
+	RocketChat.callbacks.showTotalTime = true;
+} else {
+	RocketChat.callbacks.showTime = false;
+	RocketChat.callbacks.showTotalTime = false;
+}
+
+
+/*
+* Callback priorities
+*/
+
+RocketChat.callbacks.priority = {
+	HIGH: -1000,
+	MEDIUM: 0,
+	LOW: 1000
+};
+
+
+/*
+* Add a callback function to a hook
+* @param {String} hook - The name of the hook
+* @param {Function} callback - The callback function
+*/
+
+RocketChat.callbacks.add = function(hook, callback, priority, id) {
+	if (priority == null) {
+		priority = RocketChat.callbacks.priority.MEDIUM;
+	}
+	if (!_.isNumber(priority)) {
+		priority = RocketChat.callbacks.priority.MEDIUM;
+	}
+	callback.priority = priority;
+	callback.id = id || Random.id();
+	RocketChat.callbacks[hook] = RocketChat.callbacks[hook] || [];
+	if (RocketChat.callbacks.showTime === true) {
+		const err = new Error;
+		callback.stack = err.stack;
+	}
+	if (RocketChat.callbacks[hook].find((cb) => cb.id === callback.id)) {
+		return;
+	}
+	RocketChat.callbacks[hook].push(callback);
+};
+
+
+/*
+* Remove a callback from a hook
+* @param {string} hook - The name of the hook
+* @param {string} id - The callback's id
+*/
+
+RocketChat.callbacks.remove = function(hookName, id) {
+	RocketChat.callbacks[hookName] = _.reject(RocketChat.callbacks[hookName], (callback) => callback.id === id);
+};
+
+
+/*
+* Successively run all of a hook's callbacks on an item
+* @param {String} hook - The name of the hook
+* @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks
+* @param {Object} [constant] - An optional constant that will be passed along to each callback
+* @returns {Object} Returns the item after it's been through all the callbacks for this hook
+*/
+
+RocketChat.callbacks.run = function(hook, item, constant) {
+	const callbacks = RocketChat.callbacks[hook];
+	if (callbacks && callbacks.length) {
+		let totalTime = 0;
+		const result = _.sortBy(callbacks, function(callback) {
+			return callback.priority || RocketChat.callbacks.priority.MEDIUM;
+		}).reduce(function(result, callback) {
+			let time = 0;
+			if (RocketChat.callbacks.showTime === true || RocketChat.callbacks.showTotalTime === true) {
+				time = Date.now();
+			}
+			const callbackResult = callback(result, constant);
+			if (RocketChat.callbacks.showTime === true || RocketChat.callbacks.showTotalTime === true) {
+				const currentTime = Date.now() - time;
+				totalTime += currentTime;
+				if (RocketChat.callbacks.showTime === true) {
+					if (Meteor.isServer) {
+						RocketChat.statsTracker.timing('callbacks.time', currentTime, [`hook:${ hook }`, `callback:${ callback.id }`]);
+					} else {
+						let stack = callback.stack && typeof callback.stack.split === 'function' && callback.stack.split('\n');
+						stack = stack && stack[2] && (stack[2].match(/\(.+\)/)||[])[0];
+						console.log(String(currentTime), hook, callback.id, stack);
+					}
+				}
+			}
+			return (typeof callbackResult === 'undefined') ? result : callbackResult;
+		}, item);
+		if (RocketChat.callbacks.showTotalTime === true) {
+			if (Meteor.isServer) {
+				RocketChat.statsTracker.timing('callbacks.totalTime', totalTime, [`hook:${ hook }`]);
+			} else {
+				console.log(`${ hook }:`, totalTime);
+			}
+		}
+		return result;
+	} else {
+		return item;
+	}
+};
+
+
+/*
+* Successively run all of a hook's callbacks on an item, in async mode (only works on server)
+* @param {String} hook - The name of the hook
+* @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks
+* @param {Object} [constant] - An optional constant that will be passed along to each callback
+*/
+
+RocketChat.callbacks.runAsync = function(hook, item, constant) {
+	const callbacks = RocketChat.callbacks[hook];
+	if (Meteor.isServer && callbacks && callbacks.length) {
+		Meteor.defer(function() {
+			_.sortBy(callbacks, (callback) => callback.priority || RocketChat.callbacks.priority.MEDIUM).forEach((callback) => callback(item, constant));
+		});
+	} else {
+		return item;
+	}
+};
diff --git a/packages/rocketchat-lib/lib/promises.coffee b/packages/rocketchat-lib/lib/promises.coffee
deleted file mode 100644
index a23ab907b738c7960ddf4772ecdc7081e0995736..0000000000000000000000000000000000000000
--- a/packages/rocketchat-lib/lib/promises.coffee
+++ /dev/null
@@ -1,93 +0,0 @@
-# https://github.com/TelescopeJS/Telescope/blob/master/packages/telescope-lib/lib/callbacks.js
-
-###
-# Callback hooks provide an easy way to add extra steps to common operations.
-# @namespace RocketChat.promises
-###
-RocketChat.promises = {}
-
-###
-# Callback priorities
-###
-RocketChat.promises.priority =
-	HIGH: -1000
-	MEDIUM: 0
-	LOW: 1000
-
-###
-# Add a callback function to a hook
-# @param {String} hook - The name of the hook
-# @param {Function} callback - The callback function
-###
-
-RocketChat.promises.add = (hook, callback, priority, id) ->
-	# if callback array doesn't exist yet, initialize it
-	priority ?= RocketChat.promises.priority.MEDIUM
-	unless _.isNumber priority
-		priority = RocketChat.promises.priority.MEDIUM
-	callback.priority = priority
-	callback.id = id or Random.id()
-	RocketChat.promises[hook] ?= []
-
-	# Avoid adding the same callback twice
-	for cb in RocketChat.promises[hook]
-		if cb.id is callback.id
-			return
-
-	RocketChat.promises[hook].push callback
-	return
-
-###
-# Remove a callback from a hook
-# @param {string} hook - The name of the hook
-# @param {string} id - The callback's id
-###
-
-RocketChat.promises.remove = (hookName, id) ->
-	RocketChat.promises[hookName] = _.reject RocketChat.promises[hookName], (callback) ->
-		callback.id is id
-	return
-
-###
-# Successively run all of a hook's callbacks on an item
-# @param {String} hook - The name of the hook
-# @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks
-# @param {Object} [constant] - An optional constant that will be passed along to each callback
-# @returns {Object} Returns the item after it's been through all the callbacks for this hook
-###
-
-RocketChat.promises.run = (hook, item, constant) ->
-	callbacks = RocketChat.promises[hook]
-	if !!callbacks?.length
-		# if the hook exists, and contains callbacks to run
-		callbacks = _.sortBy(callbacks, (callback) -> return callback.priority or RocketChat.promises.priority.MEDIUM)
-		return callbacks.reduce (previousPromise, callback) ->
-			return new Promise (resolve, reject) ->
-				previousPromise.then (result) ->
-					callback(result, constant).then(resolve, reject)
-		, Promise.resolve(item)
-	else
-		# else, just return the item unchanged
-		return Promise.resolve(item)
-
-###
-# Successively run all of a hook's callbacks on an item, in async mode (only works on server)
-# @param {String} hook - The name of the hook
-# @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks
-# @param {Object} [constant] - An optional constant that will be passed along to each callback
-###
-
-RocketChat.promises.runAsync = (hook, item, constant) ->
-	callbacks = RocketChat.promises[hook]
-	if Meteor.isServer and !!callbacks?.length
-		# use defer to avoid holding up client
-		Meteor.defer ->
-			# run all post submit server callbacks on post object successively
-			_.sortBy(callbacks, (callback) -> return callback.priority or RocketChat.promises.priority.MEDIUM).forEach (callback) ->
-				# console.log(callback.name);
-				callback item, constant
-				return
-			return
-	else
-		return item
-	return
diff --git a/packages/rocketchat-lib/lib/promises.js b/packages/rocketchat-lib/lib/promises.js
new file mode 100644
index 0000000000000000000000000000000000000000..1e040f172bc571ac60fcd7fda6f260e2c27fb826
--- /dev/null
+++ b/packages/rocketchat-lib/lib/promises.js
@@ -0,0 +1,89 @@
+
+/*
+* Callback hooks provide an easy way to add extra steps to common operations.
+* @namespace RocketChat.promises
+*/
+
+RocketChat.promises = {};
+
+
+/*
+* Callback priorities
+*/
+
+RocketChat.promises.priority = {
+	HIGH: -1000,
+	MEDIUM: 0,
+	LOW: 1000
+};
+
+
+/*
+* Add a callback function to a hook
+* @param {String} hook - The name of the hook
+* @param {Function} callback - The callback function
+*/
+
+RocketChat.promises.add = function(hook, callback, p = RocketChat.promises.priority.MEDIUM, id) {
+	const priority = !_.isNumber(p) ? RocketChat.promises.priority.MEDIUM : p;
+	callback.priority = priority;
+	callback.id = id || Random.id();
+	RocketChat.promises[hook] = RocketChat.promises[hook] || [];
+	if (RocketChat.promises[hook].find(cb => cb.id === callback.id)) {
+		return;
+	}
+	RocketChat.promises[hook].push(callback);
+};
+
+
+/*
+* Remove a callback from a hook
+* @param {string} hook - The name of the hook
+* @param {string} id - The callback's id
+*/
+
+RocketChat.promises.remove = function(hookName, id) {
+	RocketChat.promises[hookName] = _.reject(RocketChat.promises[hookName], (callback) => callback.id === id);
+};
+
+
+/*
+* Successively run all of a hook's callbacks on an item
+* @param {String} hook - The name of the hook
+* @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks
+* @param {Object} [constant] - An optional constant that will be passed along to each callback
+* @returns {Object} Returns the item after it's been through all the callbacks for this hook
+*/
+
+RocketChat.promises.run = function(hook, item, constant) {
+	let callbacks = RocketChat.promises[hook];
+	if (callbacks == null || callbacks.length === 0) {
+		return Promise.resolve(item);
+	}
+	callbacks = _.sortBy(callbacks, (callback) => callback.priority || RocketChat.promises.priority.MEDIUM);
+	return callbacks.reduce(function(previousPromise, callback) {
+		return new Promise(function(resolve, reject) {
+			return previousPromise.then((result) => callback(result, constant).then(resolve, reject));
+		});
+	}, Promise.resolve(item));
+};
+
+
+/*
+* Successively run all of a hook's callbacks on an item, in async mode (only works on server)
+* @param {String} hook - The name of the hook
+* @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks
+* @param {Object} [constant] - An optional constant that will be passed along to each callback
+*/
+
+RocketChat.promises.runAsync = function(hook, item, constant) {
+	const callbacks = RocketChat.promises[hook];
+	if (!Meteor.isServer || callbacks == null || callbacks.length === 0) {
+		return item;
+	}
+	Meteor.defer(() => {
+		_.sortBy(callbacks, (callback) => callback.priority || RocketChat.promises.priority.MEDIUM).forEach(function(callback) {
+			callback(item, constant);
+		});
+	});
+};
diff --git a/packages/rocketchat-lib/lib/roomTypesCommon.coffee b/packages/rocketchat-lib/lib/roomTypesCommon.coffee
deleted file mode 100644
index a351dc258922d7c20994afec0a29b84a833b54a4..0000000000000000000000000000000000000000
--- a/packages/rocketchat-lib/lib/roomTypesCommon.coffee
+++ /dev/null
@@ -1,75 +0,0 @@
-class @roomTypesCommon
-	roomTypes: {}
-	roomTypesOrder: []
-	mainOrder: 1
-
-	### Adds a room type to app
-	@param identifier An identifier to the room type. If a real room, MUST BE the same of `db.rocketchat_room.t` field, if not, can be null
-	@param order Order number of the type
-	@param config
-		template: template name to render on sideNav
-		icon: icon class
-		route:
-			name: route name
-			action: route action function
-	###
-	add: (identifier, order, config) ->
-		unless identifier?
-			identifier = Random.id()
-
-		if @roomTypes[identifier]?
-			return false
-
-		if not order?
-			order = @mainOrder + 10
-			@mainOrder += 10
-
-		# @TODO validate config options
-		@roomTypesOrder.push
-			identifier: identifier
-			order: order
-		@roomTypes[identifier] = config
-
-		if config.route?.path? and config.route?.name? and config.route?.action?
-			routeConfig =
-				name: config.route.name
-				action: config.route.action
-
-			if Meteor.isClient
-				routeConfig.triggersExit = [ roomExit ]
-
-			FlowRouter.route config.route.path, routeConfig
-
-	hasCustomLink: (roomType) ->
-		return @roomTypes[roomType]?.route?.link?
-
-	###
-	@param roomType: room type (e.g.: c (for channels), d (for direct channels))
-	@param subData: the user's subscription data
-	###
-	getRouteLink: (roomType, subData) ->
-		unless @roomTypes[roomType]?
-			return false
-
-		routeData = {}
-
-		if @roomTypes[roomType]?.route?.link?
-			routeData = @roomTypes[roomType].route.link(subData)
-		else if subData?.name?
-			routeData = { name: subData.name }
-
-		return FlowRouter.path @roomTypes[roomType].route.name, routeData
-
-	openRouteLink: (roomType, subData, queryParams) ->
-		unless @roomTypes[roomType]?
-			return false
-
-		routeData = {}
-
-		if @roomTypes[roomType]?.route?.link?
-			routeData = @roomTypes[roomType].route.link(subData)
-		else if subData?.name?
-			routeData = { name: subData.name }
-
-		return FlowRouter.go @roomTypes[roomType].route.name, routeData, queryParams
-
diff --git a/packages/rocketchat-lib/lib/roomTypesCommon.js b/packages/rocketchat-lib/lib/roomTypesCommon.js
new file mode 100644
index 0000000000000000000000000000000000000000..1df20edae0b3785391499f4e32996a5c3501bc5b
--- /dev/null
+++ b/packages/rocketchat-lib/lib/roomTypesCommon.js
@@ -0,0 +1,84 @@
+/* globals roomExit*/
+this.roomTypesCommon = class {
+	constructor() {
+		this.roomTypes = {};
+		this.roomTypesOrder = [];
+		this.mainOrder = 1;
+	}
+
+	/* Adds a room type to app
+	@param identifier An identifier to the room type. If a real room, MUST BE the same of `db.rocketchat_room.t` field, if not, can be null
+	@param order Order number of the type
+	@param config
+	template: template name to render on sideNav
+	icon: icon class
+	route:
+	name: route name
+	action: route action function
+	*/
+
+	add(identifier = Random.id(), order, config) {
+		if (this.roomTypes[identifier] != null) {
+			return false;
+		}
+		if (order == null) {
+			order = this.mainOrder + 10;
+			this.mainOrder += 10;
+		}
+		this.roomTypesOrder.push({
+			identifier,
+			order
+		});
+		this.roomTypes[identifier] = config;
+		if (config.route && config.route.path && config.route.name && config.route.action) {
+			const routeConfig = {
+				name: config.route.name,
+				action: config.route.action
+			};
+			if (Meteor.isClient) {
+				routeConfig.triggersExit = [roomExit];
+			}
+			return FlowRouter.route(config.route.path, routeConfig);
+		}
+	}
+
+	hasCustomLink(roomType) {
+		return this.roomTypes[roomType] && this.roomTypes[roomType].route && this.roomTypes[roomType].route.link != null;
+	}
+
+	/*
+	@param roomType: room type (e.g.: c (for channels), d (for direct channels))
+	@param subData: the user's subscription data
+	*/
+
+	getRouteLink(roomType, subData) {
+		if (this.roomTypes[roomType] == null) {
+			return false;
+		}
+		let routeData = {};
+		if (this.roomTypes[roomType] && this.roomTypes[roomType].route && this.roomTypes[roomType].route.link) {
+			routeData = this.roomTypes[roomType].route.link(subData);
+		} else if (subData && subData.name) {
+			routeData = {
+				name: subData.name
+			};
+		}
+		return FlowRouter.path(this.roomTypes[roomType].route.name, routeData);
+	}
+
+	openRouteLink(roomType, subData, queryParams) {
+		if (this.roomTypes[roomType] == null) {
+			return false;
+		}
+		let routeData = {};
+		if (this.roomTypes[roomType] && this.roomTypes[roomType].route && this.roomTypes[roomType].route.link) {
+			routeData = this.roomTypes[roomType].route.link(subData);
+		} else if (subData && subData.name) {
+			routeData = {
+				name: subData.name
+			};
+		}
+		return FlowRouter.go(this.roomTypes[roomType].route.name, routeData, queryParams);
+	}
+};
+export default this.roomTypesCommon;
diff --git a/packages/rocketchat-lib/lib/settings.coffee b/packages/rocketchat-lib/lib/settings.coffee
deleted file mode 100644
index cdc221064b39afb3d27c1cbac64850ee077f4b9f..0000000000000000000000000000000000000000
--- a/packages/rocketchat-lib/lib/settings.coffee
+++ /dev/null
@@ -1,83 +0,0 @@
-###
-# RocketChat.settings holds all packages settings
-# @namespace RocketChat.settings
-###
-RocketChat.settings =
-	callbacks: {}
-	regexCallbacks: {}
-	ts: new Date
-
-	get: (_id, callback) ->
-		if callback?
-			RocketChat.settings.onload _id, callback
-			if _id is '*' and Meteor.settings?
-				for key, value of Meteor.settings
-					callback key, value
-				return
-
-			if _.isRegExp(_id)
-				for key, value of Meteor.settings when _id.test(key)
-					callback key, value
-				return
-
-			if Meteor.settings?[_id]?
-				callback _id, Meteor.settings?[_id]
-		else
-			if _.isRegExp(_id)
-				items = []
-				for key, value of Meteor.settings when _id.test(key)
-					items.push
-						key: key
-						value: value
-				return items
-
-			return Meteor.settings?[_id]
-
-	set: (_id, value, callback) ->
-		Meteor.call 'saveSetting', _id, value, callback
-
-	batchSet: (settings, callback) ->
-
-		# async -> sync
-		# http://daemon.co.za/2012/04/simple-async-with-only-underscore/
-
-		save = (setting) ->
-			return (callback) ->
-				Meteor.call 'saveSetting', setting._id, setting.value, setting.editor, callback
-
-		actions = _.map settings, (setting) -> save(setting)
-		_(actions).reduceRight(_.wrap, (err, success) -> return callback err, success)()
-
-	load: (key, value, initialLoad) ->
-		if RocketChat.settings.callbacks[key]?
-			for callback in RocketChat.settings.callbacks[key]
-				callback key, value, initialLoad
-
-		if RocketChat.settings.callbacks['*']?
-			for callback in RocketChat.settings.callbacks['*']
-				callback key, value, initialLoad
-
-		for cbKey, cbValue of RocketChat.settings.regexCallbacks
-			if cbValue.regex.test(key)
-				callback(key, value, initialLoad) for callback in cbValue.callbacks
-
-
-	onload: (key, callback) ->
-		# if key is '*'
-		# 	for key, value in Meteor.settings
-		# 		callback key, value, false
-		# else if Meteor.settings?[_id]?
-		# 	callback key, Meteor.settings[_id], false
-
-		keys = [].concat key
-
-		for k in keys
-			if _.isRegExp k
-				RocketChat.settings.regexCallbacks[k.source] ?= {
-					regex: k
-					callbacks: []
-				}
-				RocketChat.settings.regexCallbacks[k.source].callbacks.push callback
-			else
-				RocketChat.settings.callbacks[k] ?= []
-				RocketChat.settings.callbacks[k].push callback
diff --git a/packages/rocketchat-lib/lib/settings.js b/packages/rocketchat-lib/lib/settings.js
new file mode 100644
index 0000000000000000000000000000000000000000..ecdd2c87c4b3e663c5d6129c4f09352c38c2f161
--- /dev/null
+++ b/packages/rocketchat-lib/lib/settings.js
@@ -0,0 +1,99 @@
+
+/*
+* RocketChat.settings holds all packages settings
+* @namespace RocketChat.settings
+*/
+RocketChat.settings = {
+	callbacks: {},
+	regexCallbacks: {},
+	ts: new Date,
+	get(_id, callback) {
+		if (callback != null) {
+			RocketChat.settings.onload(_id, callback);
+			if (!Meteor.settings) {
+				return;
+			}
+			if (_id === '*') {
+				return Object.keys(Meteor.settings).forEach(key => {
+					const value = Meteor.settings[key];
+					callback(key, value);
+				});
+			}
+			if (_.isRegExp(_id) && Meteor.settings) {
+				return Object.keys(Meteor.settings).forEach(key => {
+					if (!_id.test(key)) {
+						return;
+					}
+					const value = Meteor.settings[key];
+					callback(key, value);
+				});
+			}
+			return Meteor.settings[_id] != null && callback(_id, Meteor.settings[_id]);
+		} else {
+			if (!Meteor.settings) {
+				return;
+			}
+			if (_.isRegExp(_id)) {
+				return Object.keys(Meteor.settings).reduce((items, key) => {
+					const value = Meteor.settings[key];
+					if (_id.test(key)) {
+						items.push({
+							key,
+							value
+						});
+					}
+					return items;
+				}, []);
+			}
+			return Meteor.settings && Meteor.settings[_id];
+		}
+	},
+	set(_id, value, callback) {
+		return Meteor.call('saveSetting', _id, value, callback);
+	},
+	batchSet(settings, callback) {
+		// async -> sync
+		// http://daemon.co.za/2012/04/simple-async-with-only-underscore/
+		const save = function(setting) {
+			return function(callback) {
+				return Meteor.call('saveSetting', setting._id, setting.value, setting.editor, callback);
+			};
+		};
+		const actions = _.map(settings, (setting) => save(setting));
+		return _(actions).reduceRight(_.wrap, (err, success) => callback(err, success))();
+	},
+	load(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 => {
+			const cbValue = RocketChat.settings.regexCallbacks[cbKey];
+			if (!cbValue.regex.test(key)) {
+				return;
+			}
+			cbValue.callbacks.forEach(callback => callback(key, value, initialLoad));
+		});
+	},
+	onload(key, callback) {
+		// if key is '*'
+		// 	for key, value in Meteor.settings
+		// 		callback key, value, false
+		// else if Meteor.settings?[_id]?
+		// 	callback key, Meteor.settings[_id], false
+		const keys = [].concat(key);
+		keys.forEach(k => {
+			if (_.isRegExp(k)) {
+				RocketChat.settings.regexCallbacks[name = k.source] = RocketChat.settings.regexCallbacks[name = k.source] || {
+					regex: k,
+					callbacks: []
+				};
+				RocketChat.settings.regexCallbacks[k.source].callbacks.push(callback);
+			} else {
+				RocketChat.settings.callbacks[k] = RocketChat.settings.callbacks[k] || [];
+				RocketChat.settings.callbacks[k].push(callback);
+			}
+		});
+	}
+};
diff --git a/packages/rocketchat-lib/package.js b/packages/rocketchat-lib/package.js
index 8902fce8f2590c44633b848429ae383f1a87d275..3d7f8402927a4b2bbaa46da191ba8a3d2d5bcb2f 100644
--- a/packages/rocketchat-lib/package.js
+++ b/packages/rocketchat-lib/package.js
@@ -52,12 +52,12 @@ Package.onUse(function(api) {
 
 	// COMMON LIB
 	api.addFiles('lib/getURL.js');
-	api.addFiles('lib/settings.coffee');
-	api.addFiles('lib/callbacks.coffee');
+	api.addFiles('lib/settings.js');
+	api.addFiles('lib/callbacks.js');
 	api.addFiles('lib/fileUploadRestrictions.js');
 	api.addFiles('lib/placeholders.js');
-	api.addFiles('lib/promises.coffee');
-	api.addFiles('lib/roomTypesCommon.coffee');
+	api.addFiles('lib/promises.js');
+	api.addFiles('lib/roomTypesCommon.js');
 	api.addFiles('lib/slashCommand.js');
 	api.addFiles('lib/Message.js');
 	api.addFiles('lib/MessageTypes.js');
@@ -134,6 +134,7 @@ Package.onUse(function(api) {
 	api.addFiles('server/methods/checkRegistrationSecretURL.js', 'server');
 	api.addFiles('server/methods/cleanChannelHistory.js', 'server');
 	api.addFiles('server/methods/createChannel.js', 'server');
+	api.addFiles('server/methods/createToken.js', 'server');
 	api.addFiles('server/methods/createPrivateGroup.js', 'server');
 	api.addFiles('server/methods/deleteMessage.js', 'server');
 	api.addFiles('server/methods/deleteUserOwnAccount.js', 'server');
@@ -173,29 +174,29 @@ Package.onUse(function(api) {
 	api.addFiles('lib/startup/settingsOnLoadSiteUrl.js');
 
 	// CLIENT LIB
-	api.addFiles('client/Notifications.coffee', 'client');
+	api.addFiles('client/Notifications.js', 'client');
 	api.addFiles('client/OAuthProxy.js', 'client');
 	api.addFiles('client/lib/TabBar.js', 'client');
 	api.addFiles('client/lib/RocketChatTabBar.js', 'client');
 	api.addFiles('client/lib/cachedCollection.js', 'client');
-	api.addFiles('client/lib/openRoom.coffee', 'client');
-	api.addFiles('client/lib/roomExit.coffee', 'client');
-	api.addFiles('client/lib/settings.coffee', 'client');
-	api.addFiles('client/lib/roomTypes.coffee', 'client');
+	api.addFiles('client/lib/openRoom.js', 'client');
+	api.addFiles('client/lib/roomExit.js', 'client');
+	api.addFiles('client/lib/settings.js', 'client');
+	api.addFiles('client/lib/roomTypes.js', 'client');
 	api.addFiles('client/lib/userRoles.js', 'client');
 	api.addFiles('client/lib/Layout.js', 'client');
 
 	// CLIENT METHODS
-	api.addFiles('client/methods/sendMessage.coffee', 'client');
+	api.addFiles('client/methods/sendMessage.js', 'client');
 	api.addFiles('client/AdminBox.js', 'client');
-	api.addFiles('client/MessageAction.coffee', 'client');
+	api.addFiles('client/MessageAction.js', 'client');
 
 	api.addFiles('client/defaultTabBars.js', 'client');
 	api.addFiles('client/CustomTranslations.js', 'client');
 
 	// CLIENT MODELS
-	api.addFiles('client/models/_Base.coffee', 'client');
-	api.addFiles('client/models/Uploads.coffee', 'client');
+	api.addFiles('client/models/_Base.js', 'client');
+	api.addFiles('client/models/Uploads.js', 'client');
 
 	// CLIENT VIEWS
 	api.addFiles('client/views/customFieldsForm.html', 'client');
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/methods/createToken.js b/packages/rocketchat-lib/server/methods/createToken.js
new file mode 100644
index 0000000000000000000000000000000000000000..29879093c1c55e68427b8375a06e39b032d6f73c
--- /dev/null
+++ b/packages/rocketchat-lib/server/methods/createToken.js
@@ -0,0 +1,13 @@
+Meteor.methods({
+	createToken(userId) {
+		if (Meteor.userId() !== userId && !RocketChat.authz.hasPermission(Meteor.userId(), 'user-generate-access-token')) {
+			throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'createToken' });
+		}
+		const token = Accounts._generateStampedLoginToken();
+		Accounts._insertLoginToken(userId, token);
+		return {
+			userId,
+			authToken: token.token
+		};
+	}
+});
diff --git a/packages/rocketchat-lib/server/methods/deleteMessage.js b/packages/rocketchat-lib/server/methods/deleteMessage.js
index 73dd607fafcfff64748b52ff567d55072a65402a..f6954129df151b7c5c601afa422165c720d47c2b 100644
--- a/packages/rocketchat-lib/server/methods/deleteMessage.js
+++ b/packages/rocketchat-lib/server/methods/deleteMessage.js
@@ -23,17 +23,18 @@ Meteor.methods({
 				action: 'Delete_message'
 			});
 		}
+		const forceDelete = RocketChat.authz.hasPermission(Meteor.userId(), 'force-delete-message', originalMessage.rid);
 		const hasPermission = RocketChat.authz.hasPermission(Meteor.userId(), 'delete-message', originalMessage.rid);
 		const deleteAllowed = RocketChat.settings.get('Message_AllowDeleting');
 		const deleteOwn = originalMessage && originalMessage.u && originalMessage.u._id === Meteor.userId();
-		if (!(hasPermission || (deleteAllowed && deleteOwn))) {
+		if (!(hasPermission || (deleteAllowed && deleteOwn)) && !(forceDelete)) {
 			throw new Meteor.Error('error-action-not-allowed', 'Not allowed', {
 				method: 'deleteMessage',
 				action: 'Delete_message'
 			});
 		}
 		const blockDeleteInMinutes = RocketChat.settings.get('Message_AllowDeleting_BlockDeleteInMinutes');
-		if (blockDeleteInMinutes != null && blockDeleteInMinutes !== 0) {
+		if ((blockDeleteInMinutes != null && blockDeleteInMinutes !== 0) || !(forceDelete)) {
 			if (originalMessage.ts == null) {
 				return;
 			}
diff --git a/packages/rocketchat-lib/server/methods/getRoomRoles.js b/packages/rocketchat-lib/server/methods/getRoomRoles.js
index e35fdb3e33e96c1d93fbef85c6a9f653811a6dfe..4c7dc6c105f3e68e7544997fed42056d1bbc89c8 100644
--- a/packages/rocketchat-lib/server/methods/getRoomRoles.js
+++ b/packages/rocketchat-lib/server/methods/getRoomRoles.js
@@ -3,7 +3,7 @@ Meteor.methods({
 
 		check(rid, String);
 
-		if (!Meteor.userId() && RocketChat.settings.get('Accounts_AllowAnonymousAccess') === false) {
+		if (!Meteor.userId() && RocketChat.settings.get('Accounts_AllowAnonymousRead') === false) {
 			throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getRoomRoles' });
 		}
 
diff --git a/packages/rocketchat-lib/server/startup/settings.js b/packages/rocketchat-lib/server/startup/settings.js
index b67af0cd860ffa1d1d8b1640c0f720b5907c47ec..bd77f407b7d70c0e630c151e1142befee161be11 100644
--- a/packages/rocketchat-lib/server/startup/settings.js
+++ b/packages/rocketchat-lib/server/startup/settings.js
@@ -8,10 +8,18 @@ RocketChat.settings.add('uniqueID', process.env.DEPLOYMENT_ID || Random.id(), {
 // if you add a node to the i18n.json with the same setting name but with `_Description` it will automatically work.
 
 RocketChat.settings.addGroup('Accounts', function() {
-	this.add('Accounts_AllowAnonymousAccess', false, {
+	this.add('Accounts_AllowAnonymousRead', false, {
 		type: 'boolean',
 		public: true
 	});
+	this.add('Accounts_AllowAnonymousWrite', false, {
+		type: 'boolean',
+		public: true,
+		enableQuery: {
+			_id: 'Accounts_AllowAnonymousRead',
+			value: true
+		}
+	});
 	this.add('Accounts_AllowDeleteOwnAccount', false, {
 		type: 'boolean',
 		'public': true,
@@ -62,7 +70,11 @@ RocketChat.settings.addGroup('Accounts', function() {
 		type: 'boolean',
 		'public': true
 	});
+
 	this.section('Registration', function() {
+		this.add('Accounts_DefaultUsernamePrefixSuggestion', 'user', {
+			type: 'string'
+		});
 		this.add('Accounts_RequireNameForSignUp', true, {
 			type: 'boolean',
 			'public': true
@@ -145,6 +157,7 @@ RocketChat.settings.addGroup('Accounts', function() {
 			i18nLabel: 'Custom_Fields'
 		});
 	});
+
 	this.section('Avatar', function() {
 		this.add('Accounts_AvatarResize', true, {
 			type: 'boolean'
@@ -468,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,
@@ -478,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-lib/startup/defaultRoomTypes.js b/packages/rocketchat-lib/startup/defaultRoomTypes.js
index 692ddb8765284bb94e760d9c2c1c6760df209af1..40fe79c9daf12417d55d6102592f5fc557f7f05b 100644
--- a/packages/rocketchat-lib/startup/defaultRoomTypes.js
+++ b/packages/rocketchat-lib/startup/defaultRoomTypes.js
@@ -28,7 +28,7 @@ RocketChat.roomTypes.add('c', 10, {
 	},
 
 	condition() {
-		return RocketChat.authz.hasAtLeastOnePermission(['view-c-room', 'view-joined-room']) || RocketChat.settings.get('Accounts_AllowAnonymousAccess') === true;
+		return RocketChat.authz.hasAtLeastOnePermission(['view-c-room', 'view-joined-room']) || RocketChat.settings.get('Accounts_AllowAnonymousRead') === true;
 	},
 
 	showJoinLink(roomId) {
diff --git a/packages/rocketchat-livechat/app/.meteor/packages b/packages/rocketchat-livechat/app/.meteor/packages
index e8e4d7068409ad119d230806037aeefbd3b29503..e777a77fc027b856e0c3128f4b8034b3089d4457 100644
--- a/packages/rocketchat-livechat/app/.meteor/packages
+++ b/packages/rocketchat-livechat/app/.meteor/packages
@@ -7,12 +7,12 @@
 meteor@1.6.1
 webapp@1.3.15
 logging@1.1.17
-tracker@1.1.2
+tracker@1.1.3
 deps@1.0.12
 session@1.1.7
 ddp@1.2.5
 livedata@1.0.18
-mongo@1.1.16
+mongo@1.1.17
 blaze
 ui
 spacebars
@@ -32,10 +32,10 @@ underscorestring:underscore.string
 momentjs:moment
 mizzao:timesync
 reactive-var@1.0.11
-accounts-password@1.3.5
+accounts-password@1.3.6
 tap:i18n
 smoral:sweetalert
-ecmascript@0.7.2
+ecmascript@0.7.3
 es5-shim@4.6.15
 standard-minifier-css@1.3.4
 standard-minifier-js@2.0.0
diff --git a/packages/rocketchat-livechat/app/.meteor/release b/packages/rocketchat-livechat/app/.meteor/release
index 605b4e1f03103658e752cf5bad58f52bee90009f..fb6f3bc15e2230094f0495e9d4a67d2bcf537d9d 100644
--- a/packages/rocketchat-livechat/app/.meteor/release
+++ b/packages/rocketchat-livechat/app/.meteor/release
@@ -1 +1 @@
-METEOR@1.4.4.1
+METEOR@1.4.4.2
diff --git a/packages/rocketchat-livechat/app/.meteor/versions b/packages/rocketchat-livechat/app/.meteor/versions
index 8e1dfbd6e4f6eb146dda3f29bbb87c2cba260bb8..5d13dcd9ea34598eaebb66e74e3c25ba977be183 100644
--- a/packages/rocketchat-livechat/app/.meteor/versions
+++ b/packages/rocketchat-livechat/app/.meteor/versions
@@ -1,5 +1,5 @@
-accounts-base@1.2.16
-accounts-password@1.3.5
+accounts-base@1.2.17
+accounts-password@1.3.6
 aldeed:simple-schema@1.5.3
 allow-deny@1.0.5
 babel-compiler@6.18.2
@@ -45,12 +45,12 @@ meteor@1.6.1
 meteorspark:util@0.2.0
 minifier-css@1.2.16
 minifier-js@2.0.0
-minimongo@1.0.21
+minimongo@1.0.23
 mizzao:timesync@0.5.0
 modules@0.8.2
 modules-runtime@0.7.10
 momentjs:moment@2.18.1
-mongo@1.1.16
+mongo@1.1.17
 mongo-id@1.0.6
 npm-bcrypt@0.9.2
 npm-mongo@2.2.24
@@ -80,7 +80,7 @@ templating@1.3.2
 templating-compiler@1.3.2
 templating-runtime@1.3.2
 templating-tools@1.1.2
-tracker@1.1.2
+tracker@1.1.3
 ui@1.0.13
 underscore@1.0.10
 underscorestring:underscore.string@3.3.4
diff --git a/packages/rocketchat-livechat/app/i18n/bg.i18n.json b/packages/rocketchat-livechat/app/i18n/bg.i18n.json
index 060a3ee2788019a3cf1c2f2a23b454cfd9ec0b62..2a6ec769e46c9d6270cd01faf807e95888a3a2cd 100644
--- a/packages/rocketchat-livechat/app/i18n/bg.i18n.json
+++ b/packages/rocketchat-livechat/app/i18n/bg.i18n.json
@@ -1,6 +1,7 @@
 {
   "Are_you_sure_do_you_want_end_this_chat": "Сигурен ли си че искаш да прекратиш този чат?",
   "Cancel": "Отказ",
+  "Change": "Промени",
   "Chat_ended": "Край на чата",
   "Close_menu": "Затваряне на менюто",
   "Conversation_finished": "Разговорът завърши",
diff --git a/packages/rocketchat-livechat/app/package.json b/packages/rocketchat-livechat/app/package.json
index de148775e58610cf02cf0204bbcff6b0465e828f..e0e9add70d3b88c2dc600aa5dc8e000f4fc04ec4 100644
--- a/packages/rocketchat-livechat/app/package.json
+++ b/packages/rocketchat-livechat/app/package.json
@@ -20,11 +20,11 @@
     "email": "support@rocket.chat"
   },
   "dependencies": {
-    "autolinker": "^1.4.2",
-    "jquery": "^3.1.1",
+    "autolinker": "^1.4.3",
+    "jquery": "^3.2.1",
     "babel-runtime": "^6.23.0",
     "bcrypt": "^1.0.2",
-    "moment": "^2.17.1",
+    "moment": "^2.18.1",
     "toastr": "^2.1.2"
   }
 }
diff --git a/packages/rocketchat-mailer/client/router.coffee b/packages/rocketchat-mailer/client/router.coffee
deleted file mode 100644
index 0314ec8fd1ea3959b6e97ec2e2bb4735b93304f6..0000000000000000000000000000000000000000
--- a/packages/rocketchat-mailer/client/router.coffee
+++ /dev/null
@@ -1,10 +0,0 @@
-FlowRouter.route '/mailer',
-	name: 'mailer'
-	action: ->
-		BlazeLayout.render 'main', {center: 'mailer'}
-
-FlowRouter.route '/mailer/unsubscribe/:_id/:createdAt',
-	name: 'mailer-unsubscribe'
-	action: (params) ->
-		Meteor.call 'Mailer:unsubscribe', params._id, params.createdAt
-		BlazeLayout.render 'mailerUnsubscribe'
diff --git a/packages/rocketchat-mailer/client/router.js b/packages/rocketchat-mailer/client/router.js
new file mode 100644
index 0000000000000000000000000000000000000000..e2a9e67280355aa5c241a0271f07314ed5d329a1
--- /dev/null
+++ b/packages/rocketchat-mailer/client/router.js
@@ -0,0 +1,16 @@
+FlowRouter.route('/mailer', {
+	name: 'mailer',
+	action() {
+		return BlazeLayout.render('main', {
+			center: 'mailer'
+		});
+	}
+});
+
+FlowRouter.route('/mailer/unsubscribe/:_id/:createdAt', {
+	name: 'mailer-unsubscribe',
+	action(params) {
+		Meteor.call('Mailer:unsubscribe', params._id, params.createdAt);
+		return BlazeLayout.render('mailerUnsubscribe');
+	}
+});
diff --git a/packages/rocketchat-mailer/client/startup.coffee b/packages/rocketchat-mailer/client/startup.coffee
deleted file mode 100644
index 9b4dd0847261dc4f7f71d1158fd0f217e0814d35..0000000000000000000000000000000000000000
--- a/packages/rocketchat-mailer/client/startup.coffee
+++ /dev/null
@@ -1,5 +0,0 @@
-RocketChat.AdminBox.addOption
-	href: 'mailer'
-	i18nLabel: 'Mailer'
-	permissionGranted: ->
-		return RocketChat.authz.hasAllPermission('access-mailer')
diff --git a/packages/rocketchat-mailer/client/startup.js b/packages/rocketchat-mailer/client/startup.js
new file mode 100644
index 0000000000000000000000000000000000000000..728ebddf8c1014144472793f98e99fdffaf86368
--- /dev/null
+++ b/packages/rocketchat-mailer/client/startup.js
@@ -0,0 +1,7 @@
+RocketChat.AdminBox.addOption({
+	href: 'mailer',
+	i18nLabel: 'Mailer',
+	permissionGranted() {
+		return RocketChat.authz.hasAllPermission('access-mailer');
+	}
+});
diff --git a/packages/rocketchat-mailer/client/views/mailer.coffee b/packages/rocketchat-mailer/client/views/mailer.coffee
deleted file mode 100644
index 9f484b5a5e345d353375d478749152ae0a9e704b..0000000000000000000000000000000000000000
--- a/packages/rocketchat-mailer/client/views/mailer.coffee
+++ /dev/null
@@ -1,25 +0,0 @@
-import toastr from 'toastr'
-Template.mailer.helpers
-	fromEmail: ->
-		return RocketChat.settings.get 'From_Email'
-
-Template.mailer.events
-	'click .send': (e, t) ->
-		e.preventDefault()
-		from = $(t.find('[name=from]')).val()
-		subject = $(t.find('[name=subject]')).val()
-		body = $(t.find('[name=body]')).val()
-		dryrun = $(t.find('[name=dryrun]:checked')).val()
-		query = $(t.find('[name=query]')).val()
-
-		unless from
-			toastr.error TAPi18n.__('error-invalid-from-address')
-			return
-
-		if body.indexOf('[unsubscribe]') is -1
-			toastr.error TAPi18n.__('error-missing-unsubscribe-link')
-			return
-
-		Meteor.call 'Mailer.sendMail', from, subject, body, dryrun, query, (err) ->
-			return handleError(err) if err
-			toastr.success TAPi18n.__('The_emails_are_being_sent')
diff --git a/packages/rocketchat-mailer/client/views/mailer.js b/packages/rocketchat-mailer/client/views/mailer.js
new file mode 100644
index 0000000000000000000000000000000000000000..731df5aaa3ec0f6fb09ce42645818ac09eb48286
--- /dev/null
+++ b/packages/rocketchat-mailer/client/views/mailer.js
@@ -0,0 +1,31 @@
+import toastr from 'toastr';
+Template.mailer.helpers({
+	fromEmail() {
+		return RocketChat.settings.get('From_Email');
+	}
+});
+
+Template.mailer.events({
+	'click .send'(e, t) {
+		e.preventDefault();
+		const from = $(t.find('[name=from]')).val();
+		const subject = $(t.find('[name=subject]')).val();
+		const body = $(t.find('[name=body]')).val();
+		const dryrun = $(t.find('[name=dryrun]:checked')).val();
+		const query = $(t.find('[name=query]')).val();
+		if (!from) {
+			toastr.error(TAPi18n.__('error-invalid-from-address'));
+			return;
+		}
+		if (body.indexOf('[unsubscribe]') === -1) {
+			toastr.error(TAPi18n.__('error-missing-unsubscribe-link'));
+			return;
+		}
+		return Meteor.call('Mailer.sendMail', from, subject, body, dryrun, query, function(err) {
+			if (err) {
+				return handleError(err);
+			}
+			return toastr.success(TAPi18n.__('The_emails_are_being_sent'));
+		});
+	}
+});
diff --git a/packages/rocketchat-mailer/client/views/mailerUnsubscribe.coffee b/packages/rocketchat-mailer/client/views/mailerUnsubscribe.coffee
deleted file mode 100644
index d8a7a1840e01af98ba3f803df8ee52f8c724aa63..0000000000000000000000000000000000000000
--- a/packages/rocketchat-mailer/client/views/mailerUnsubscribe.coffee
+++ /dev/null
@@ -1,2 +0,0 @@
-Template.mailerUnsubscribe.onRendered ->
-	$('#initial-page-loading').remove()
diff --git a/packages/rocketchat-mailer/client/views/mailerUnsubscribe.js b/packages/rocketchat-mailer/client/views/mailerUnsubscribe.js
new file mode 100644
index 0000000000000000000000000000000000000000..8012c4deb519cb4039414465ff3b241eeee98699
--- /dev/null
+++ b/packages/rocketchat-mailer/client/views/mailerUnsubscribe.js
@@ -0,0 +1,3 @@
+Template.mailerUnsubscribe.onRendered(function() {
+	return $('#initial-page-loading').remove();
+});
diff --git a/packages/rocketchat-mailer/lib/Mailer.coffee b/packages/rocketchat-mailer/lib/Mailer.coffee
deleted file mode 100644
index 8158d6d741d11f0781290697aa859da139136b1b..0000000000000000000000000000000000000000
--- a/packages/rocketchat-mailer/lib/Mailer.coffee
+++ /dev/null
@@ -1 +0,0 @@
-Mailer = {}
diff --git a/packages/rocketchat-mailer/lib/Mailer.js b/packages/rocketchat-mailer/lib/Mailer.js
new file mode 100644
index 0000000000000000000000000000000000000000..e7e30cfebe4a4c3d77958073b2cd07714b135695
--- /dev/null
+++ b/packages/rocketchat-mailer/lib/Mailer.js
@@ -0,0 +1 @@
+Mailer = {};//eslint-disable-line
diff --git a/packages/rocketchat-mailer/package.js b/packages/rocketchat-mailer/package.js
index 30da5c981e53b3f17c01c813ba9681bd096a3d18..ffe0a7e317dc37cf5ddc210c3fa700448494c03f 100644
--- a/packages/rocketchat-mailer/package.js
+++ b/packages/rocketchat-mailer/package.js
@@ -7,7 +7,6 @@ Package.describe({
 Package.onUse(function(api) {
 	api.use([
 		'ecmascript',
-		'coffeescript',
 		'ddp-rate-limiter',
 		'kadira:flow-router',
 		'rocketchat:lib',
@@ -16,24 +15,24 @@ Package.onUse(function(api) {
 
 	api.use('templating', 'client');
 
-	api.addFiles('lib/Mailer.coffee');
+	api.addFiles('lib/Mailer.js');
 
 	api.addFiles([
-		'client/startup.coffee',
-		'client/router.coffee',
+		'client/startup.js',
+		'client/router.js',
 		'client/views/mailer.html',
-		'client/views/mailer.coffee',
+		'client/views/mailer.js',
 		'client/views/mailerUnsubscribe.html',
-		'client/views/mailerUnsubscribe.coffee'
+		'client/views/mailerUnsubscribe.js'
 	], 'client');
 
 	api.addFiles([
-		'server/startup.coffee',
-		'server/models/Users.coffee',
-		'server/functions/sendMail.coffee',
-		'server/functions/unsubscribe.coffee',
-		'server/methods/sendMail.coffee',
-		'server/methods/unsubscribe.coffee'
+		'server/startup.js',
+		'server/models/Users.js',
+		'server/functions/sendMail.js',
+		'server/functions/unsubscribe.js',
+		'server/methods/sendMail.js',
+		'server/methods/unsubscribe.js'
 	], 'server');
 
 	api.export('Mailer');
diff --git a/packages/rocketchat-mailer/server/functions/sendMail.coffee b/packages/rocketchat-mailer/server/functions/sendMail.coffee
deleted file mode 100644
index d6fa86beb87f9ccf0f3f069139e278970520f7ad..0000000000000000000000000000000000000000
--- a/packages/rocketchat-mailer/server/functions/sendMail.coffee
+++ /dev/null
@@ -1,63 +0,0 @@
-Mailer.sendMail = (from, subject, body, dryrun, query) ->
-
-	rfcMailPatternWithName = /^(?:.*<)?([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)(?:>?)$/
-	# rfcMailPattern = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
-
-	unless rfcMailPatternWithName.test from
-		throw new Meteor.Error 'error-invalid-from-address', 'Invalid from address', { function: 'Mailer.sendMail' }
-
-	if body.indexOf('[unsubscribe]') is -1
-		throw new Meteor.Error 'error-missing-unsubscribe-link', 'You must provide the [unsubscribe] link.', { function: 'Mailer.sendMail' }
-
-	header = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Header') || '');
-	footer = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer') || '');
-
-	userQuery = { "mailer.unsubscribed": { $exists: 0 } }
-	if query
-		userQuery = { $and: [ userQuery, EJSON.parse(query) ] }
-
-	if dryrun
-		Meteor.users.find({ "emails.address": from }).forEach (user) ->
-		# Meteor.users.find({ "username": /\.rocket\.team/ }).forEach (user) ->
-			email = user.emails?[0]?.address
-
-			html = RocketChat.placeholders.replace(body, {
-				unsubscribe: Meteor.absoluteUrl(FlowRouter.path('mailer/unsubscribe/:_id/:createdAt', { _id: user._id, createdAt: user.createdAt.getTime() })),
-				name: user.name,
-				email: email
-			});
-
-			email = "#{user.name} <#{email}>"
-
-			if rfcMailPatternWithName.test email
-				Meteor.defer ->
-					Email.send
-						to: email
-						from: from
-						subject: subject
-						html: header + html + footer
-
-				console.log 'Sending email to ' + email
-
-	else
-		Meteor.users.find({ "mailer.unsubscribed": { $exists: 0 } }).forEach (user) ->
-		# Meteor.users.find({ "username": /\.rocket\.team/ }).forEach (user) ->
-			email = user.emails?[0]?.address
-
-			html = RocketChat.placeholders.replace(body, {
-				unsubscribe: Meteor.absoluteUrl(FlowRouter.path('mailer/unsubscribe/:_id/:createdAt', { _id: user._id, createdAt: user.createdAt.getTime() })),
-				name: user.name,
-				email: email
-			});
-
-			email = "#{user.name} <#{email}>"
-
-			if rfcMailPatternWithName.test email
-				Meteor.defer ->
-					Email.send
-						to: email
-						from: from
-						subject: subject
-						html: header + html + footer
-
-				console.log 'Sending email to ' + email
diff --git a/packages/rocketchat-mailer/server/functions/sendMail.js b/packages/rocketchat-mailer/server/functions/sendMail.js
new file mode 100644
index 0000000000000000000000000000000000000000..b8cd2e9b49bec5dabcabc91a8155bd27505ecc64
--- /dev/null
+++ b/packages/rocketchat-mailer/server/functions/sendMail.js
@@ -0,0 +1,80 @@
+/*globals Mailer */
+Mailer.sendMail = function(from, subject, body, dryrun, query) {
+
+	const rfcMailPatternWithName = /^(?:.*<)?([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)(?:>?)$/;
+	if (!rfcMailPatternWithName.test(from)) {
+		throw new Meteor.Error('error-invalid-from-address', 'Invalid from address', {
+			'function': 'Mailer.sendMail'
+		});
+	}
+	if (body.indexOf('[unsubscribe]') === -1) {
+		throw new Meteor.Error('error-missing-unsubscribe-link', 'You must provide the [unsubscribe] link.', {
+			'function': 'Mailer.sendMail'
+		});
+	}
+	const header = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Header') || '');
+	const footer = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer') || '');
+
+	let userQuery = { 'mailer.unsubscribed': { $exists: 0 } };
+	if (query) {
+		userQuery = { $and: [ userQuery, EJSON.parse(query) ] };
+	}
+
+	if (dryrun) {
+		return Meteor.users.find({
+			'emails.address': from
+		}).forEach((user) => {
+			let email = undefined;
+			if (user.emails && user.emails[0] && user.emails[0].address) {
+				email = user.emails[0].address;
+			}
+			const html = RocketChat.placeholders.replace(body, {
+				unsubscribe: Meteor.absoluteUrl(FlowRouter.path('mailer/unsubscribe/:_id/:createdAt', {
+					_id: user._id,
+					createdAt: user.createdAt.getTime()
+				})),
+				name: user.name,
+				email
+			});
+			email = `${ user.name } <${ email }>`;
+			if (rfcMailPatternWithName.test(email)) {
+				Meteor.defer(function() {
+					return Email.send({
+						to: email,
+						from,
+						subject,
+						html: header + html + footer
+					});
+				});
+				return console.log(`Sending email to ${ email }`);
+			}
+		});
+	} else {
+		return Meteor.users.find(userQuery).forEach(function(user) {
+			let email = undefined;
+			if (user.emails && user.emails[0] && user.emails[0].address) {
+				email = user.emails[0].address;
+			}
+			const html = RocketChat.placeholders.replace(body, {
+				unsubscribe: Meteor.absoluteUrl(FlowRouter.path('mailer/unsubscribe/:_id/:createdAt', {
+					_id: user._id,
+					createdAt: user.createdAt.getTime()
+				})),
+				name: user.name,
+				email
+			});
+			email = `${ user.name } <${ email }>`;
+			if (rfcMailPatternWithName.test(email)) {
+				Meteor.defer(function() {
+					return Email.send({
+						to: email,
+						from,
+						subject,
+						html: header + html + footer
+					});
+				});
+				return console.log(`Sending email to ${ email }`);
+			}
+		});
+	}
+};
diff --git a/packages/rocketchat-mailer/server/functions/unsubscribe.coffee b/packages/rocketchat-mailer/server/functions/unsubscribe.coffee
deleted file mode 100644
index 955a0ec95fb313f980d6b2afc0f7d06fa04e592d..0000000000000000000000000000000000000000
--- a/packages/rocketchat-mailer/server/functions/unsubscribe.coffee
+++ /dev/null
@@ -1,4 +0,0 @@
-Mailer.unsubscribe = (_id, createdAt) ->
-	if _id and createdAt
-		return RocketChat.models.Users.RocketMailUnsubscribe(_id, createdAt) == 1
-	return false
diff --git a/packages/rocketchat-mailer/server/functions/unsubscribe.js b/packages/rocketchat-mailer/server/functions/unsubscribe.js
new file mode 100644
index 0000000000000000000000000000000000000000..4e0bbc838ad92425c3994f109612b3f8fbb88f63
--- /dev/null
+++ b/packages/rocketchat-mailer/server/functions/unsubscribe.js
@@ -0,0 +1,7 @@
+/* globals Mailer */
+Mailer.unsubscribe = function(_id, createdAt) {
+	if (_id && createdAt) {
+		return RocketChat.models.Users.rocketMailUnsubscribe(_id, createdAt) === 1;
+	}
+	return false;
+};
diff --git a/packages/rocketchat-mailer/server/methods/sendMail.coffee b/packages/rocketchat-mailer/server/methods/sendMail.coffee
deleted file mode 100644
index 17b8946353f5aea16236118bd2cd5f5c6ddbd92c..0000000000000000000000000000000000000000
--- a/packages/rocketchat-mailer/server/methods/sendMail.coffee
+++ /dev/null
@@ -1,16 +0,0 @@
-Meteor.methods
-	'Mailer.sendMail': (from, subject, body, dryrun, query) ->
-		if not Meteor.userId()
-			throw new Meteor.Error 'error-invalid-user', "Invalid user", { method: 'Mailer.sendMail' }
-
-		unless RocketChat.authz.hasRole( Meteor.userId(), 'admin') is true
-			throw new Meteor.Error 'error-not-allowed', 'Not allowed', { method: 'Mailer.sendMail' }
-
-		return Mailer.sendMail from, subject, body, dryrun, query
-
-# Limit setting username once per minute
-# DDPRateLimiter.addRule
-# 	type: 'method'
-# 	name: 'Mailer.sendMail'
-# 	connectionId: -> return true
-# , 1, 60000
diff --git a/packages/rocketchat-mailer/server/methods/sendMail.js b/packages/rocketchat-mailer/server/methods/sendMail.js
new file mode 100644
index 0000000000000000000000000000000000000000..c45b394b01b277c6e692302b196d8a634f1b0005
--- /dev/null
+++ b/packages/rocketchat-mailer/server/methods/sendMail.js
@@ -0,0 +1,25 @@
+/*globals Mailer */
+Meteor.methods({
+	'Mailer.sendMail'(from, subject, body, dryrun, query) {
+		const userId = Meteor.userId();
+		if (!userId) {
+			throw new Meteor.Error('error-invalid-user', 'Invalid user', {
+				method: 'Mailer.sendMail'
+			});
+		}
+		if (RocketChat.authz.hasRole(userId, 'admin') !== true) {
+			throw new Meteor.Error('error-not-allowed', 'Not allowed', {
+				method: 'Mailer.sendMail'
+			});
+		}
+		return Mailer.sendMail(from, subject, body, dryrun, query);
+	}
+});
+
+
+//Limit setting username once per minute
+//DDPRateLimiter.addRule
+//	type: 'method'
+//	name: 'Mailer.sendMail'
+//	connectionId: -> return true
+//	, 1, 60000
diff --git a/packages/rocketchat-mailer/server/methods/unsubscribe.coffee b/packages/rocketchat-mailer/server/methods/unsubscribe.coffee
deleted file mode 100644
index 88e5d7a1494b65d965f9247aa4de02b87aa2efa0..0000000000000000000000000000000000000000
--- a/packages/rocketchat-mailer/server/methods/unsubscribe.coffee
+++ /dev/null
@@ -1,10 +0,0 @@
-Meteor.methods
-	'Mailer:unsubscribe': (_id, createdAt) ->
-		return Mailer.unsubscribe _id, createdAt
-
-# Limit setting username once per minute
-DDPRateLimiter.addRule
-	type: 'method'
-	name: 'Mailer:unsubscribe'
-	connectionId: -> return true
-, 1, 60000
diff --git a/packages/rocketchat-mailer/server/methods/unsubscribe.js b/packages/rocketchat-mailer/server/methods/unsubscribe.js
new file mode 100644
index 0000000000000000000000000000000000000000..914c80e7b143456edb6996b0cd4f181a5b2b7291
--- /dev/null
+++ b/packages/rocketchat-mailer/server/methods/unsubscribe.js
@@ -0,0 +1,14 @@
+/*globals Mailer */
+Meteor.methods({
+	'Mailer:unsubscribe'(_id, createdAt) {
+		return Mailer.unsubscribe(_id, createdAt);
+	}
+});
+
+DDPRateLimiter.addRule({
+	type: 'method',
+	name: 'Mailer:unsubscribe',
+	connectionId() {
+		return true;
+	}
+}, 1, 60000);
diff --git a/packages/rocketchat-mailer/server/models/Users.coffee b/packages/rocketchat-mailer/server/models/Users.coffee
deleted file mode 100644
index 7622b890d4809369954c0e09763e1ecbce80b329..0000000000000000000000000000000000000000
--- a/packages/rocketchat-mailer/server/models/Users.coffee
+++ /dev/null
@@ -1,17 +0,0 @@
-# Extends model Users
-
-RocketChat.models.Users.RocketMailUnsubscribe = (_id, createdAt) ->
-
-	query =
-		_id: _id
-		createdAt: new Date(parseInt createdAt)
-
-	update =
-		$set:
-			"mailer.unsubscribed": true
-
-	affectedRows = @update query, update
-
-	console.log '[Mailer:Unsubscribe]', _id, createdAt, new Date(parseInt createdAt), affectedRows
-
-	return affectedRows
diff --git a/packages/rocketchat-mailer/server/models/Users.js b/packages/rocketchat-mailer/server/models/Users.js
new file mode 100644
index 0000000000000000000000000000000000000000..9649db1972ffecb03a5db198dbf31a0ca4db173c
--- /dev/null
+++ b/packages/rocketchat-mailer/server/models/Users.js
@@ -0,0 +1,14 @@
+RocketChat.models.Users.rocketMailUnsubscribe = function(_id, createdAt) {
+	const query = {
+		_id,
+		createdAt: new Date(parseInt(createdAt))
+	};
+	const update = {
+		$set: {
+			'mailer.unsubscribed': true
+		}
+	};
+	const affectedRows = this.update(query, update);
+	console.log('[Mailer:Unsubscribe]', _id, createdAt, new Date(parseInt(createdAt)), affectedRows);
+	return affectedRows;
+};
diff --git a/packages/rocketchat-mailer/server/startup.coffee b/packages/rocketchat-mailer/server/startup.coffee
deleted file mode 100644
index acf55f73c49df2b434547e248b79b422e084518c..0000000000000000000000000000000000000000
--- a/packages/rocketchat-mailer/server/startup.coffee
+++ /dev/null
@@ -1,2 +0,0 @@
-Meteor.startup ->
-	RocketChat.models.Permissions.upsert( 'access-mailer', { $setOnInsert : { _id: 'access-mailer', roles : ['admin'] } })
diff --git a/packages/rocketchat-mailer/server/startup.js b/packages/rocketchat-mailer/server/startup.js
new file mode 100644
index 0000000000000000000000000000000000000000..e1ef976a18fd59c81f886ca65c3b5111a2acbc9d
--- /dev/null
+++ b/packages/rocketchat-mailer/server/startup.js
@@ -0,0 +1,8 @@
+Meteor.startup(function() {
+	return RocketChat.models.Permissions.upsert('access-mailer', {
+		$setOnInsert: {
+			_id: 'access-mailer',
+			roles: ['admin']
+		}
+	});
+});
diff --git a/packages/rocketchat-mentions-flextab/client/actionButton.coffee b/packages/rocketchat-mentions-flextab/client/actionButton.coffee
deleted file mode 100644
index 84d350c96809238dd64ee447fa82b9fb6f70dc01..0000000000000000000000000000000000000000
--- a/packages/rocketchat-mentions-flextab/client/actionButton.coffee
+++ /dev/null
@@ -1,17 +0,0 @@
-Meteor.startup ->
-	RocketChat.MessageAction.addButton
-		id: 'jump-to-message'
-		icon: 'icon-right-hand'
-		i18nLabel: 'Jump_to_message'
-		context: [
-			'mentions'
-		]
-		action: (event, instance) ->
-			message = @_arguments[1]
-			RocketChat.MessageAction.hideDropDown()
-			RoomHistoryManager.getSurroundingMessages(message, 50)
-
-		validation: (message) ->
-			return message.mentionedList is true
-
-		order: 100
diff --git a/packages/rocketchat-mentions-flextab/client/actionButton.js b/packages/rocketchat-mentions-flextab/client/actionButton.js
new file mode 100644
index 0000000000000000000000000000000000000000..9f23356936370ed44b1b37934e9976b276d4e322
--- /dev/null
+++ b/packages/rocketchat-mentions-flextab/client/actionButton.js
@@ -0,0 +1,17 @@
+Meteor.startup(function() {
+	return RocketChat.MessageAction.addButton({
+		id: 'jump-to-message',
+		icon: 'icon-right-hand',
+		i18nLabel: 'Jump_to_message',
+		context: ['mentions'],
+		action() {
+			const message = this._arguments[1];
+			RocketChat.MessageAction.hideDropDown();
+			return RoomHistoryManager.getSurroundingMessages(message, 50);
+		},
+		validation(message) {
+			return message.mentionedList === true;
+		},
+		order: 100
+	});
+});
diff --git a/packages/rocketchat-mentions-flextab/client/lib/MentionedMessage.coffee b/packages/rocketchat-mentions-flextab/client/lib/MentionedMessage.coffee
deleted file mode 100644
index 98325aa643a805811600daaba200572b90cd1678..0000000000000000000000000000000000000000
--- a/packages/rocketchat-mentions-flextab/client/lib/MentionedMessage.coffee
+++ /dev/null
@@ -1 +0,0 @@
-@MentionedMessage = new Mongo.Collection 'rocketchat_mentioned_message'
diff --git a/packages/rocketchat-mentions-flextab/client/lib/MentionedMessage.js b/packages/rocketchat-mentions-flextab/client/lib/MentionedMessage.js
new file mode 100644
index 0000000000000000000000000000000000000000..fffbdd257ebaf74d11c554db5d620bca337c7315
--- /dev/null
+++ b/packages/rocketchat-mentions-flextab/client/lib/MentionedMessage.js
@@ -0,0 +1 @@
+this.MentionedMessage = new Mongo.Collection('rocketchat_mentioned_message');
diff --git a/packages/rocketchat-mentions-flextab/client/tabBar.coffee b/packages/rocketchat-mentions-flextab/client/tabBar.js
similarity index 64%
rename from packages/rocketchat-mentions-flextab/client/tabBar.coffee
rename to packages/rocketchat-mentions-flextab/client/tabBar.js
index 70b75724e52f8a349f8e8d369f5741695b9e2dd1..272b29ede3151612408c01851b246753fa1931ff 100644
--- a/packages/rocketchat-mentions-flextab/client/tabBar.coffee
+++ b/packages/rocketchat-mentions-flextab/client/tabBar.js
@@ -1,9 +1,10 @@
-Meteor.startup ->
-	RocketChat.TabBar.addButton({
+Meteor.startup(function() {
+	return RocketChat.TabBar.addButton({
 		groups: ['channel', 'group'],
 		id: 'mentions',
 		i18nTitle: 'Mentions',
 		icon: 'icon-at',
 		template: 'mentionsFlexTab',
 		order: 3
-	})
+	});
+});
diff --git a/packages/rocketchat-mentions-flextab/client/views/mentionsFlexTab.coffee b/packages/rocketchat-mentions-flextab/client/views/mentionsFlexTab.coffee
deleted file mode 100644
index 0d6fab788374077235d6cc1416dedd939ae8e9ff..0000000000000000000000000000000000000000
--- a/packages/rocketchat-mentions-flextab/client/views/mentionsFlexTab.coffee
+++ /dev/null
@@ -1,39 +0,0 @@
-Template.mentionsFlexTab.helpers
-	hasMessages: ->
-		return MentionedMessage.find({ rid: @rid }, { sort: { ts: -1 } }).count() > 0
-
-	messages: ->
-		return MentionedMessage.find { rid: @rid }, { sort: { ts: -1 } }
-
-	message: ->
-		return _.extend(this, { customClass: 'mentions' })
-
-	hasMore: ->
-		return Template.instance().hasMore.get()
-
-Template.mentionsFlexTab.onCreated ->
-	@hasMore = new ReactiveVar true
-	@limit = new ReactiveVar 50
-	@autorun =>
-		@subscribe 'mentionedMessages', @data.rid, @limit.get(), =>
-			if MentionedMessage.find({ rid: @data.rid }).count() < @limit.get()
-				@hasMore.set false
-
-Template.mentionsFlexTab.events
-	'click .message-cog': (e, t) ->
-		e.stopPropagation()
-		e.preventDefault()
-		message_id = $(e.currentTarget).closest('.message').attr('id')
-		RocketChat.MessageAction.hideDropDown()
-		t.$("\##{message_id} .message-dropdown").remove()
-		message = MentionedMessage.findOne message_id
-		actions = RocketChat.MessageAction.getButtons message, 'mentions'
-		el = Blaze.toHTMLWithData Template.messageDropdown, { actions: actions }
-		t.$("\##{message_id} .message-cog-container").append el
-		dropDown = t.$("\##{message_id} .message-dropdown")
-		dropDown.show()
-
-	'scroll .content': _.throttle (e, instance) ->
-		if e.target.scrollTop >= e.target.scrollHeight - e.target.clientHeight && instance.hasMore.get()
-			instance.limit.set(instance.limit.get() + 50)
-	, 200
diff --git a/packages/rocketchat-mentions-flextab/client/views/mentionsFlexTab.js b/packages/rocketchat-mentions-flextab/client/views/mentionsFlexTab.js
new file mode 100644
index 0000000000000000000000000000000000000000..a929120d1595007963978d08724c5b76e6942c2c
--- /dev/null
+++ b/packages/rocketchat-mentions-flextab/client/views/mentionsFlexTab.js
@@ -0,0 +1,65 @@
+/*globals MentionedMessage */
+Template.mentionsFlexTab.helpers({
+	hasMessages() {
+		return MentionedMessage.find({
+			rid: this.rid
+		}, {
+			sort: {
+				ts: -1
+			}
+		}).count() > 0;
+	},
+	messages() {
+		return MentionedMessage.find({
+			rid: this.rid
+		}, {
+			sort: {
+				ts: -1
+			}
+		});
+	},
+	message() {
+		return _.extend(this, {
+			customClass: 'mentions'
+		});
+	},
+	hasMore() {
+		return Template.instance().hasMore.get();
+	}
+});
+
+Template.mentionsFlexTab.onCreated(function() {
+	this.hasMore = new ReactiveVar(true);
+	this.limit = new ReactiveVar(50);
+	return this.autorun(() => {
+		const mentionedMessageFind = MentionedMessage.find({ rid: this.data.rid });
+		return this.subscribe('mentionedMessages', this.data.rid, this.limit.get(), () => {
+			if (mentionedMessageFind.count() < this.limit.get()) {
+				return this.hasMore.set(false);
+			}
+		});
+	});
+});
+
+Template.mentionsFlexTab.events({
+	'click .message-cog'(e, t) {
+		e.stopPropagation();
+		e.preventDefault();
+		const message_id = $(e.currentTarget).closest('.message').attr('id');
+		RocketChat.MessageAction.hideDropDown();
+		t.$(`\#${ message_id } .message-dropdown`).remove();
+		const message = MentionedMessage.findOne(message_id);
+		const actions = RocketChat.MessageAction.getButtons(message, 'mentions');
+		const el = Blaze.toHTMLWithData(Template.messageDropdown, {
+			actions
+		});
+		t.$(`\#${ message_id } .message-cog-container`).append(el);
+		const dropDown = t.$(`\#${ message_id } .message-dropdown`);
+		return dropDown.show();
+	},
+	'scroll .content': _.throttle(function(e, instance) {
+		if (e.target.scrollTop >= e.target.scrollHeight - e.target.clientHeight && instance.hasMore.get()) {
+			return instance.limit.set(instance.limit.get() + 50);
+		}
+	}, 200)
+});
diff --git a/packages/rocketchat-mentions-flextab/package.js b/packages/rocketchat-mentions-flextab/package.js
index 127ecc9f6997a23195a2c4076ebd7f8c0849cefe..4f38223f73ae80521663cdd2fc775416404359c5 100644
--- a/packages/rocketchat-mentions-flextab/package.js
+++ b/packages/rocketchat-mentions-flextab/package.js
@@ -9,7 +9,6 @@ Package.onUse(function(api) {
 	api.use([
 		'mongo',
 		'ecmascript',
-		'coffeescript',
 		'underscore',
 		'less',
 		'rocketchat:lib'
@@ -18,15 +17,15 @@ Package.onUse(function(api) {
 	api.use('templating', 'client');
 
 	api.addFiles([
-		'client/lib/MentionedMessage.coffee',
+		'client/lib/MentionedMessage.js',
 		'client/views/stylesheets/mentionsFlexTab.less',
 		'client/views/mentionsFlexTab.html',
-		'client/views/mentionsFlexTab.coffee',
-		'client/actionButton.coffee',
-		'client/tabBar.coffee'
+		'client/views/mentionsFlexTab.js',
+		'client/actionButton.js',
+		'client/tabBar.js'
 	], 'client');
 
 	api.addFiles([
-		'server/publications/mentionedMessages.coffee'
+		'server/publications/mentionedMessages.js'
 	], 'server');
 });
diff --git a/packages/rocketchat-mentions-flextab/server/publications/mentionedMessages.coffee b/packages/rocketchat-mentions-flextab/server/publications/mentionedMessages.coffee
deleted file mode 100644
index 72a557e2df8547cc83ad88e5db2f1d8ed441f68e..0000000000000000000000000000000000000000
--- a/packages/rocketchat-mentions-flextab/server/publications/mentionedMessages.coffee
+++ /dev/null
@@ -1,25 +0,0 @@
-Meteor.publish 'mentionedMessages', (rid, limit=50) ->
-	unless this.userId
-		return this.ready()
-
-	publication = @
-
-	user = RocketChat.models.Users.findOneById this.userId
-	unless user
-		return this.ready()
-
-	cursorHandle = RocketChat.models.Messages.findVisibleByMentionAndRoomId(user.username, rid, { sort: { ts: -1 }, limit: limit }).observeChanges
-		added: (_id, record) ->
-			record.mentionedList = true
-			publication.added('rocketchat_mentioned_message', _id, record)
-
-		changed: (_id, record) ->
-			record.mentionedList = true
-			publication.changed('rocketchat_mentioned_message', _id, record)
-
-		removed: (_id) ->
-			publication.removed('rocketchat_mentioned_message', _id)
-
-	@ready()
-	@onStop ->
-		cursorHandle.stop()
diff --git a/packages/rocketchat-mentions-flextab/server/publications/mentionedMessages.js b/packages/rocketchat-mentions-flextab/server/publications/mentionedMessages.js
new file mode 100644
index 0000000000000000000000000000000000000000..994454f9fd86b080875e92bc8118277975df9934
--- /dev/null
+++ b/packages/rocketchat-mentions-flextab/server/publications/mentionedMessages.js
@@ -0,0 +1,32 @@
+Meteor.publish('mentionedMessages', function(rid, limit = 50) {
+	if (!this.userId) {
+		return this.ready();
+	}
+	const publication = this;
+	const user = RocketChat.models.Users.findOneById(this.userId);
+	if (!user) {
+		return this.ready();
+	}
+	const cursorHandle = RocketChat.models.Messages.findVisibleByMentionAndRoomId(user.username, rid, {
+		sort: {
+			ts: -1
+		},
+		limit
+	}).observeChanges({
+		added(_id, record) {
+			record.mentionedList = true;
+			return publication.added('rocketchat_mentioned_message', _id, record);
+		},
+		changed(_id, record) {
+			record.mentionedList = true;
+			return publication.changed('rocketchat_mentioned_message', _id, record);
+		},
+		removed(_id) {
+			return publication.removed('rocketchat_mentioned_message', _id);
+		}
+	});
+	this.ready();
+	return this.onStop(function() {
+		return cursorHandle.stop();
+	});
+});
diff --git a/packages/rocketchat-message-star/client/actionButton.coffee b/packages/rocketchat-message-star/client/actionButton.coffee
deleted file mode 100644
index c0b873a6db8c7d4ffcd1649287db1d4762bd4389..0000000000000000000000000000000000000000
--- a/packages/rocketchat-message-star/client/actionButton.coffee
+++ /dev/null
@@ -1,83 +0,0 @@
-import toastr from 'toastr'
-Meteor.startup ->
-	RocketChat.MessageAction.addButton
-		id: 'star-message'
-		icon: 'icon-star-empty'
-		i18nLabel: 'Star_Message'
-		context: [
-			'starred'
-			'message'
-			'message-mobile'
-		]
-		action: (event, instance) ->
-			message = @_arguments[1]
-			message.starred = Meteor.userId()
-			Meteor.call 'starMessage', message, (error, result) ->
-				if error
-					return handleError(error)
-		validation: (message) ->
-			if not RocketChat.models.Subscriptions.findOne({ rid: message.rid })?
-				return false
-
-			return RocketChat.settings.get('Message_AllowStarring') and not message.starred
-		order: 10
-
-	RocketChat.MessageAction.addButton
-		id: 'unstar-message'
-		icon: 'icon-star'
-		i18nLabel: 'Unstar_Message'
-		context: [
-			'starred'
-			'message'
-			'message-mobile'
-		]
-		action: (event, instance) ->
-			message = @_arguments[1]
-			message.starred = false
-			Meteor.call 'starMessage', message, (error, result) ->
-				if error
-					return handleError(error)
-		validation: (message) ->
-			if not RocketChat.models.Subscriptions.findOne({ rid: message.rid })?
-				return false
-
-			return RocketChat.settings.get('Message_AllowStarring') and message.starred
-		order: 10
-
-	RocketChat.MessageAction.addButton
-		id: 'jump-to-star-message'
-		icon: 'icon-right-hand'
-		i18nLabel: 'Jump_to_message'
-		context: [
-			'starred'
-		]
-		action: (event, instance) ->
-			message = @_arguments[1]
-			RocketChat.MessageAction.hideDropDown()
-			RoomHistoryManager.getSurroundingMessages(message, 50)
-		validation: (message) ->
-			if not RocketChat.models.Subscriptions.findOne({ rid: message.rid })?
-				return false
-
-			return true
-		order: 100
-
-	RocketChat.MessageAction.addButton
-		id: 'permalink-star'
-		icon: 'icon-link'
-		i18nLabel: 'Permalink'
-		classes: 'clipboard'
-		context: [
-			'starred'
-		]
-		action: (event, instance) ->
-			message = @_arguments[1]
-			RocketChat.MessageAction.hideDropDown()
-			$(event.currentTarget).attr('data-clipboard-text', RocketChat.MessageAction.getPermaLink(message._id));
-			toastr.success(TAPi18n.__('Copied'))
-		validation: (message) ->
-			if not RocketChat.models.Subscriptions.findOne({ rid: message.rid })?
-				return false
-
-			return true
-		order: 101
diff --git a/packages/rocketchat-message-star/client/actionButton.js b/packages/rocketchat-message-star/client/actionButton.js
new file mode 100644
index 0000000000000000000000000000000000000000..64faa7b24bad0a06d5a4ed31201cabe92d651393
--- /dev/null
+++ b/packages/rocketchat-message-star/client/actionButton.js
@@ -0,0 +1,87 @@
+import toastr from 'toastr';
+Meteor.startup(function() {
+	RocketChat.MessageAction.addButton({
+		id: 'star-message',
+		icon: 'icon-star-empty',
+		i18nLabel: 'Star_Message',
+		context: ['starred', 'message', 'message-mobile'],
+		action() {
+			const message = this._arguments[1];
+			message.starred = Meteor.userId();
+			return Meteor.call('starMessage', message, function(error) {
+				if (error) {
+					return handleError(error);
+				}
+			});
+		},
+		validation(message) {
+			if (RocketChat.models.Subscriptions.findOne({ rid: message.rid }) == null && RocketChat.settings.get('Message_AllowStarring')) {
+				return false;
+			}
+
+			return !_.findWhere(message.starred, {_id: Meteor.userId()});
+		},
+		order: 10
+	});
+	RocketChat.MessageAction.addButton({
+		id: 'unstar-message',
+		icon: 'icon-star',
+		i18nLabel: 'Unstar_Message',
+		context: ['starred', 'message', 'message-mobile'],
+		action() {
+			const message = this._arguments[1];
+			message.starred = false;
+			return Meteor.call('starMessage', message, function(error) {
+				if (error) {
+					return handleError(error);
+				}
+			});
+		},
+		validation(message) {
+			if (RocketChat.models.Subscriptions.findOne({ rid: message.rid }) == null && RocketChat.settings.get('Message_AllowStarring')) {
+				return false;
+			}
+
+			return Boolean(_.findWhere(message.starred, {_id: Meteor.userId()}));
+		},
+		order: 10
+	});
+	RocketChat.MessageAction.addButton({
+		id: 'jump-to-star-message',
+		icon: 'icon-right-hand',
+		i18nLabel: 'Jump_to_message',
+		context: ['starred'],
+		action() {
+			const message = this._arguments[1];
+			RocketChat.MessageAction.hideDropDown();
+			return RoomHistoryManager.getSurroundingMessages(message, 50);
+		},
+		validation(message) {
+			if (RocketChat.models.Subscriptions.findOne({ rid: message.rid }) == null) {
+				return false;
+			}
+			return true;
+		},
+		order: 100
+	});
+	return RocketChat.MessageAction.addButton({
+		id: 'permalink-star',
+		icon: 'icon-link',
+		i18nLabel: 'Permalink',
+		classes: 'clipboard',
+		context: ['starred'],
+		action() {
+			const message = this._arguments[1];
+			RocketChat.MessageAction.hideDropDown();
+			$(event.currentTarget).attr('data-clipboard-text', RocketChat.MessageAction.getPermaLink(message._id));
+			return toastr.success(TAPi18n.__('Copied'));
+		},
+		validation(message) {
+			if (RocketChat.models.Subscriptions.findOne({ rid: message.rid }) == null) {
+				return false;
+			}
+			return true;
+		},
+		order: 101
+	});
+});
diff --git a/packages/rocketchat-message-star/client/lib/StarredMessage.coffee b/packages/rocketchat-message-star/client/lib/StarredMessage.coffee
deleted file mode 100644
index 10798a3a464b65df232b04fcb03eab3f3a07e24d..0000000000000000000000000000000000000000
--- a/packages/rocketchat-message-star/client/lib/StarredMessage.coffee
+++ /dev/null
@@ -1 +0,0 @@
-@StarredMessage = new Mongo.Collection 'rocketchat_starred_message'
diff --git a/packages/rocketchat-message-star/client/lib/StarredMessage.js b/packages/rocketchat-message-star/client/lib/StarredMessage.js
new file mode 100644
index 0000000000000000000000000000000000000000..cd014935ac3f37de474a8b0dc32a8491de9055d9
--- /dev/null
+++ b/packages/rocketchat-message-star/client/lib/StarredMessage.js
@@ -0,0 +1 @@
+this.StarredMessage = new Mongo.Collection('rocketchat_starred_message');
diff --git a/packages/rocketchat-message-star/client/starMessage.coffee b/packages/rocketchat-message-star/client/starMessage.coffee
deleted file mode 100644
index d6b22b9488a88e1b5c343cce0223c6d9211fd34f..0000000000000000000000000000000000000000
--- a/packages/rocketchat-message-star/client/starMessage.coffee
+++ /dev/null
@@ -1,15 +0,0 @@
-Meteor.methods
-	starMessage: (message) ->
-		if not Meteor.userId()
-			return false
-
-		if not RocketChat.models.Subscriptions.findOne({ rid: message.rid })?
-			return false
-
-		if not RocketChat.settings.get 'Message_AllowStarring'
-			return false
-
-		ChatMessage.update
-			_id: message._id
-		,
-			$set: { starred: !!message.starred }
diff --git a/packages/rocketchat-message-star/client/starMessage.js b/packages/rocketchat-message-star/client/starMessage.js
new file mode 100644
index 0000000000000000000000000000000000000000..cae539c7eaa17823e19674dfa744c7c6bfe76883
--- /dev/null
+++ b/packages/rocketchat-message-star/client/starMessage.js
@@ -0,0 +1,20 @@
+Meteor.methods({
+	starMessage(message) {
+		if (!Meteor.userId()) {
+			return false;
+		}
+		if (RocketChat.models.Subscriptions.findOne({ rid: message.rid }) == null) {
+			return false;
+		}
+		if (!RocketChat.settings.get('Message_AllowStarring')) {
+			return false;
+		}
+		return ChatMessage.update({
+			_id: message._id
+		}, {
+			$set: {
+				starred: !!message.starred
+			}
+		});
+	}
+});
diff --git a/packages/rocketchat-message-star/client/tabBar.coffee b/packages/rocketchat-message-star/client/tabBar.js
similarity index 68%
rename from packages/rocketchat-message-star/client/tabBar.coffee
rename to packages/rocketchat-message-star/client/tabBar.js
index cab66321912a2664ecd4425ff5a1a10599bb0e2b..85f32f93349ae28cfcbd2afe183cb656316583cd 100644
--- a/packages/rocketchat-message-star/client/tabBar.coffee
+++ b/packages/rocketchat-message-star/client/tabBar.js
@@ -1,9 +1,10 @@
-Meteor.startup ->
-	RocketChat.TabBar.addButton({
+Meteor.startup(function() {
+	return RocketChat.TabBar.addButton({
 		groups: ['channel', 'group', 'direct'],
 		id: 'starred-messages',
 		i18nTitle: 'Starred_Messages',
 		icon: 'icon-star',
 		template: 'starredMessages',
 		order: 3
-	})
+	});
+});
diff --git a/packages/rocketchat-message-star/client/views/starredMessages.coffee b/packages/rocketchat-message-star/client/views/starredMessages.coffee
deleted file mode 100644
index 9b34d62693657bbd9864ef50e7d1b8cc139e06df..0000000000000000000000000000000000000000
--- a/packages/rocketchat-message-star/client/views/starredMessages.coffee
+++ /dev/null
@@ -1,40 +0,0 @@
-Template.starredMessages.helpers
-	hasMessages: ->
-		return StarredMessage.find({ rid: @rid }, { sort: { ts: -1 } }).count() > 0
-
-	messages: ->
-		return StarredMessage.find { rid: @rid }, { sort: { ts: -1 } }
-
-	message: ->
-		return _.extend(this, { customClass: 'starred' })
-
-	hasMore: ->
-		return Template.instance().hasMore.get()
-
-Template.starredMessages.onCreated ->
-	@hasMore = new ReactiveVar true
-	@limit = new ReactiveVar 50
-	@autorun =>
-		sub = @subscribe 'starredMessages', @data.rid, @limit.get()
-		if sub.ready()
-			if StarredMessage.find({ rid: @data.rid }).count() < @limit.get()
-				@hasMore.set false
-
-Template.starredMessages.events
-	'click .message-cog': (e, t) ->
-		e.stopPropagation()
-		e.preventDefault()
-		message_id = $(e.currentTarget).closest('.message').attr('id')
-		RocketChat.MessageAction.hideDropDown()
-		t.$("\##{message_id} .message-dropdown").remove()
-		message = StarredMessage.findOne message_id
-		actions = RocketChat.MessageAction.getButtons message, 'starred'
-		el = Blaze.toHTMLWithData Template.messageDropdown, { actions: actions }
-		t.$("\##{message_id} .message-cog-container").append el
-		dropDown = t.$("\##{message_id} .message-dropdown")
-		dropDown.show()
-
-	'scroll .content': _.throttle (e, instance) ->
-		if e.target.scrollTop >= e.target.scrollHeight - e.target.clientHeight
-			instance.limit.set(instance.limit.get() + 50)
-	, 200
diff --git a/packages/rocketchat-message-star/client/views/starredMessages.js b/packages/rocketchat-message-star/client/views/starredMessages.js
new file mode 100644
index 0000000000000000000000000000000000000000..eed8d3d0bacaefbbc01b893e24e3386047d5ce7e
--- /dev/null
+++ b/packages/rocketchat-message-star/client/views/starredMessages.js
@@ -0,0 +1,66 @@
+/*globals StarredMessage */
+Template.starredMessages.helpers({
+	hasMessages() {
+		return StarredMessage.find({
+			rid: this.rid
+		}, {
+			sort: {
+				ts: -1
+			}
+		}).count() > 0;
+	},
+	messages() {
+		return StarredMessage.find({
+			rid: this.rid
+		}, {
+			sort: {
+				ts: -1
+			}
+		});
+	},
+	message() {
+		return _.extend(this, {
+			customClass: 'starred'
+		});
+	},
+	hasMore() {
+		return Template.instance().hasMore.get();
+	}
+});
+
+Template.starredMessages.onCreated(function() {
+	this.hasMore = new ReactiveVar(true);
+	this.limit = new ReactiveVar(50);
+	this.autorun(() => {
+		const sub = this.subscribe('starredMessages', this.data.rid, this.limit.get());
+		const findStarredMessage = StarredMessage.find({ rid: this.data.rid });
+		if (sub.ready()) {
+			if (findStarredMessage.count() < this.limit.get()) {
+				return this.hasMore.set(false);
+			}
+		}
+	});
+});
+
+Template.starredMessages.events({
+	'click .message-cog'(e, t) {
+		e.stopPropagation();
+		e.preventDefault();
+		const message_id = $(e.currentTarget).closest('.message').attr('id');
+		RocketChat.MessageAction.hideDropDown();
+		t.$(`\#${ message_id } .message-dropdown`).remove();
+		const message = StarredMessage.findOne(message_id);
+		const actions = RocketChat.MessageAction.getButtons(message, 'starred');
+		const el = Blaze.toHTMLWithData(Template.messageDropdown, {
+			actions
+		});
+		t.$(`\#${ message_id } .message-cog-container`).append(el);
+		const dropDown = t.$(`\#${ message_id } .message-dropdown`);
+		return dropDown.show();
+	},
+	'scroll .content': _.throttle(function(e, instance) {
+		if (e.target.scrollTop >= e.target.scrollHeight - e.target.clientHeight) {
+			return instance.limit.set(instance.limit.get() + 50);
+		}
+	}, 200)
+});
diff --git a/packages/rocketchat-message-star/package.js b/packages/rocketchat-message-star/package.js
index c9bcefca8cb120edd5802a439676777e88904809..b0e1f3e6b6bbeae56abb0a94790dc968946119e3 100644
--- a/packages/rocketchat-message-star/package.js
+++ b/packages/rocketchat-message-star/package.js
@@ -8,7 +8,6 @@ Package.describe({
 Package.onUse(function(api) {
 	api.use([
 		'mongo',
-		'coffeescript',
 		'ecmascript',
 		'underscore',
 		'less',
@@ -18,19 +17,19 @@ Package.onUse(function(api) {
 	api.use('templating', 'client');
 
 	api.addFiles([
-		'client/lib/StarredMessage.coffee',
-		'client/actionButton.coffee',
-		'client/starMessage.coffee',
-		'client/tabBar.coffee',
+		'client/lib/StarredMessage.js',
+		'client/actionButton.js',
+		'client/starMessage.js',
+		'client/tabBar.js',
 		'client/views/starredMessages.html',
-		'client/views/starredMessages.coffee',
+		'client/views/starredMessages.js',
 		'client/views/stylesheets/messagestar.less'
 	], 'client');
 
 	api.addFiles([
-		'server/settings.coffee',
-		'server/starMessage.coffee',
-		'server/publications/starredMessages.coffee',
-		'server/startup/indexes.coffee'
+		'server/settings.js',
+		'server/starMessage.js',
+		'server/publications/starredMessages.js',
+		'server/startup/indexes.js'
 	], 'server');
 });
diff --git a/packages/rocketchat-message-star/server/publications/starredMessages.coffee b/packages/rocketchat-message-star/server/publications/starredMessages.coffee
deleted file mode 100644
index 51d664d7653035ed39bf3d9dcee23267e501e0a5..0000000000000000000000000000000000000000
--- a/packages/rocketchat-message-star/server/publications/starredMessages.coffee
+++ /dev/null
@@ -1,23 +0,0 @@
-Meteor.publish 'starredMessages', (rid, limit=50) ->
-	unless this.userId
-		return this.ready()
-
-	publication = @
-
-	user = RocketChat.models.Users.findOneById this.userId
-	unless user
-		return this.ready()
-
-	cursorHandle = RocketChat.models.Messages.findStarredByUserAtRoom(this.userId, rid, { sort: { ts: -1 }, limit: limit }).observeChanges
-		added: (_id, record) ->
-			publication.added('rocketchat_starred_message', _id, record)
-
-		changed: (_id, record) ->
-			publication.changed('rocketchat_starred_message', _id, record)
-
-		removed: (_id) ->
-			publication.removed('rocketchat_starred_message', _id)
-
-	@ready()
-	@onStop ->
-		cursorHandle.stop()
diff --git a/packages/rocketchat-message-star/server/publications/starredMessages.js b/packages/rocketchat-message-star/server/publications/starredMessages.js
new file mode 100644
index 0000000000000000000000000000000000000000..15446bebeef5fef3110d9a978160dedb8397f450
--- /dev/null
+++ b/packages/rocketchat-message-star/server/publications/starredMessages.js
@@ -0,0 +1,30 @@
+Meteor.publish('starredMessages', function(rid, limit = 50) {
+	if (!this.userId) {
+		return this.ready();
+	}
+	const publication = this;
+	const user = RocketChat.models.Users.findOneById(this.userId);
+	if (!user) {
+		return this.ready();
+	}
+	const cursorHandle = RocketChat.models.Messages.findStarredByUserAtRoom(this.userId, rid, {
+		sort: {
+			ts: -1
+		},
+		limit
+	}).observeChanges({
+		added(_id, record) {
+			return publication.added('rocketchat_starred_message', _id, record);
+		},
+		changed(_id, record) {
+			return publication.changed('rocketchat_starred_message', _id, record);
+		},
+		removed(_id) {
+			return publication.removed('rocketchat_starred_message', _id);
+		}
+	});
+	this.ready();
+	return this.onStop(function() {
+		return cursorHandle.stop();
+	});
+});
diff --git a/packages/rocketchat-message-star/server/settings.coffee b/packages/rocketchat-message-star/server/settings.coffee
deleted file mode 100644
index de6bb651c8f7d570b0146559cef38109a4f3b726..0000000000000000000000000000000000000000
--- a/packages/rocketchat-message-star/server/settings.coffee
+++ /dev/null
@@ -1,2 +0,0 @@
-Meteor.startup ->
-	RocketChat.settings.add 'Message_AllowStarring', true, { type: 'boolean', group: 'Message', public: true }
diff --git a/packages/rocketchat-message-star/server/settings.js b/packages/rocketchat-message-star/server/settings.js
new file mode 100644
index 0000000000000000000000000000000000000000..4a8fa47af713b9fcec53d4d25cdc9b7606abf971
--- /dev/null
+++ b/packages/rocketchat-message-star/server/settings.js
@@ -0,0 +1,7 @@
+Meteor.startup(function() {
+	return RocketChat.settings.add('Message_AllowStarring', true, {
+		type: 'boolean',
+		group: 'Message',
+		'public': true
+	});
+});
diff --git a/packages/rocketchat-message-star/server/starMessage.coffee b/packages/rocketchat-message-star/server/starMessage.coffee
deleted file mode 100644
index 030fa908162809354ad6ff34a9ca66fe48784731..0000000000000000000000000000000000000000
--- a/packages/rocketchat-message-star/server/starMessage.coffee
+++ /dev/null
@@ -1,14 +0,0 @@
-Meteor.methods
-	starMessage: (message) ->
-		if not Meteor.userId()
-			throw new Meteor.Error('error-invalid-user', "Invalid user", { method: 'starMessage' })
-
-		if not RocketChat.settings.get 'Message_AllowStarring'
-			throw new Meteor.Error 'error-action-not-allowed', 'Message starring not allowed', { method: 'pinMessage', action: 'Message_starring' }
-
-		room = RocketChat.models.Rooms.findOneById(message.rid)
-
-		if Array.isArray(room.usernames) && room.usernames.indexOf(Meteor.user().username) is -1
-			return false
-
-		RocketChat.models.Messages.updateUserStarById(message._id, Meteor.userId(), message.starred)
diff --git a/packages/rocketchat-message-star/server/starMessage.js b/packages/rocketchat-message-star/server/starMessage.js
new file mode 100644
index 0000000000000000000000000000000000000000..3ed7ee3bd8b6d85828fe0b5459ab5e0f3e5ad56f
--- /dev/null
+++ b/packages/rocketchat-message-star/server/starMessage.js
@@ -0,0 +1,21 @@
+Meteor.methods({
+	starMessage(message) {
+		if (!Meteor.userId()) {
+			throw new Meteor.Error('error-invalid-user', 'Invalid user', {
+				method: 'starMessage'
+			});
+		}
+		if (!RocketChat.settings.get('Message_AllowStarring')) {
+			throw new Meteor.Error('error-action-not-allowed', 'Message starring not allowed', {
+				method: 'pinMessage',
+				action: 'Message_starring'
+			});
+		}
+		const room = RocketChat.models.Rooms.findOneById(message.rid);
+		if (Array.isArray(room.usernames) && room.usernames.indexOf(Meteor.user().username) === -1) {
+			return false;
+		}
+		return RocketChat.models.Messages.updateUserStarById(message._id, Meteor.userId(), message.starred);
+	}
+});
+
diff --git a/packages/rocketchat-message-star/server/startup/indexes.coffee b/packages/rocketchat-message-star/server/startup/indexes.coffee
deleted file mode 100644
index 519eb19b596f6e1b31f27770cf0fedbb6e6fead5..0000000000000000000000000000000000000000
--- a/packages/rocketchat-message-star/server/startup/indexes.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-Meteor.startup ->
-	Meteor.defer ->
-		RocketChat.models.Messages.tryEnsureIndex { 'starred._id': 1 }, { sparse: 1 }
diff --git a/packages/rocketchat-message-star/server/startup/indexes.js b/packages/rocketchat-message-star/server/startup/indexes.js
new file mode 100644
index 0000000000000000000000000000000000000000..f1946d6f4eb17f89ccc13dbcc355a7c3bc61065d
--- /dev/null
+++ b/packages/rocketchat-message-star/server/startup/indexes.js
@@ -0,0 +1,9 @@
+Meteor.startup(function() {
+	return Meteor.defer(function() {
+		return RocketChat.models.Messages.tryEnsureIndex({
+			'starred._id': 1
+		}, {
+			sparse: 1
+		});
+	});
+});
diff --git a/packages/rocketchat-oembed/client/baseWidget.coffee b/packages/rocketchat-oembed/client/baseWidget.coffee
deleted file mode 100644
index 09a090712d9da95784da1947701f41e47b82ad71..0000000000000000000000000000000000000000
--- a/packages/rocketchat-oembed/client/baseWidget.coffee
+++ /dev/null
@@ -1,21 +0,0 @@
-Template.oembedBaseWidget.helpers
-	template: ->
-		if this._overrideTemplate
-			return this._overrideTemplate
-
-		if this.headers?.contentType?.match(/image\/.*/)?
-			return 'oembedImageWidget'
-
-		if this.headers?.contentType?.match(/audio\/.*/)?
-			return 'oembedAudioWidget'
-
-		if this.headers?.contentType?.match(/video\/.*/)? or this.meta?.twitterPlayerStreamContentType?.match(/video\/.*/)?
-			return 'oembedVideoWidget'
-
-		if this.meta?.oembedHtml?
-			return 'oembedFrameWidget'
-
-		if this.meta?.sandstorm?.grain?
-			return 'oembedSandstormGrain'
-
-		return 'oembedUrlWidget'
diff --git a/packages/rocketchat-oembed/client/baseWidget.js b/packages/rocketchat-oembed/client/baseWidget.js
new file mode 100644
index 0000000000000000000000000000000000000000..1b8c95278b2dec361e915573d9004d23a1abb7cf
--- /dev/null
+++ b/packages/rocketchat-oembed/client/baseWidget.js
@@ -0,0 +1,28 @@
+Template.oembedBaseWidget.helpers({
+	template() {
+		let contentType;
+		if (this.headers) {
+			contentType = this.headers.contentType;
+		}
+
+		if (this._overrideTemplate) {
+			return this._overrideTemplate;
+		}
+		if (this.headers && contentType && contentType.match(/image\/.*/)) {
+			return 'oembedImageWidget';
+		}
+		if (this.headers && contentType && contentType.match(/audio\/.*/)) {
+			return 'oembedAudioWidget';
+		}
+		if ((this.headers && contentType && contentType.match(/video\/.*/)) || (this.meta && this.meta.twitterPlayerStreamContentType && this.meta.twitterPlayerStreamContentType.match(/video\/.*/))) {
+			return 'oembedVideoWidget';
+		}
+		if (this.meta && this.meta.oembedHtml) {
+			return 'oembedFrameWidget';
+		}
+		if (this.meta && this.meta.sandstorm && this.meta.sandstorm.grain) {
+			return 'oembedSandstormGrain';
+		}
+		return 'oembedUrlWidget';
+	}
+});
diff --git a/packages/rocketchat-oembed/client/oembedAudioWidget.coffee b/packages/rocketchat-oembed/client/oembedAudioWidget.coffee
deleted file mode 100644
index 03756dc6da528f9c39a491c4f01a39f5beeb6cd6..0000000000000000000000000000000000000000
--- a/packages/rocketchat-oembed/client/oembedAudioWidget.coffee
+++ /dev/null
@@ -1,7 +0,0 @@
-Template.oembedAudioWidget.helpers
-
-	collapsed: ->
-		if this.collapsed?
-			return this.collapsed
-		else
-			return Meteor.user()?.settings?.preferences?.collapseMediaByDefault is true
diff --git a/packages/rocketchat-oembed/client/oembedAudioWidget.js b/packages/rocketchat-oembed/client/oembedAudioWidget.js
new file mode 100644
index 0000000000000000000000000000000000000000..23bc9a4d7c125f78174f5f9faaa5f69b28dc9d61
--- /dev/null
+++ b/packages/rocketchat-oembed/client/oembedAudioWidget.js
@@ -0,0 +1,10 @@
+Template.oembedAudioWidget.helpers({
+	collapsed() {
+		if (this.collapsed) {
+			return this.collapsed;
+		} else {
+			const user = Meteor.user();
+			return user && user.settings && user.settings.preferences && user.settings.preferences.collapseMediaByDefault === true;
+		}
+	}
+});
diff --git a/packages/rocketchat-oembed/client/oembedFrameWidget.coffee b/packages/rocketchat-oembed/client/oembedFrameWidget.coffee
deleted file mode 100644
index d589454c1e873d8eef823460a9cd2fb1ddaf4d6d..0000000000000000000000000000000000000000
--- a/packages/rocketchat-oembed/client/oembedFrameWidget.coffee
+++ /dev/null
@@ -1,7 +0,0 @@
-Template.oembedFrameWidget.helpers
-
-	collapsed: ->
-		if this.collapsed?
-			return this.collapsed
-		else
-			return Meteor.user()?.settings?.preferences?.collapseMediaByDefault is true
diff --git a/packages/rocketchat-oembed/client/oembedFrameWidget.js b/packages/rocketchat-oembed/client/oembedFrameWidget.js
new file mode 100644
index 0000000000000000000000000000000000000000..e47f62edca66dee2e1bad2cd5094a5e4b359bfcd
--- /dev/null
+++ b/packages/rocketchat-oembed/client/oembedFrameWidget.js
@@ -0,0 +1,10 @@
+Template.oembedFrameWidget.helpers({
+	collapsed() {
+		if (this.collapsed) {
+			return this.collapsed;
+		} else {
+			const user = Meteor.user();
+			return user && user.settings && user.settings.preferences && user.settings.preferences.collapseMediaByDefault === true;
+		}
+	}
+});
diff --git a/packages/rocketchat-oembed/client/oembedImageWidget.coffee b/packages/rocketchat-oembed/client/oembedImageWidget.coffee
deleted file mode 100644
index 794442111178561f07682578c9fd2129440835ca..0000000000000000000000000000000000000000
--- a/packages/rocketchat-oembed/client/oembedImageWidget.coffee
+++ /dev/null
@@ -1,17 +0,0 @@
-Template.oembedImageWidget.helpers
-	loadImage: ->
-
-		if Meteor.user()?.settings?.preferences?.autoImageLoad is false and this.downloadImages? is not true
-			return false
-
-		if Meteor.Device.isPhone() and Meteor.user()?.settings?.preferences?.saveMobileBandwidth and this.downloadImages? is not true
-			return false
-
-		return true
-
-
-	collapsed: ->
-		if this.collapsed?
-			return this.collapsed
-		else
-			return Meteor.user()?.settings?.preferences?.collapseMediaByDefault is true
diff --git a/packages/rocketchat-oembed/client/oembedImageWidget.js b/packages/rocketchat-oembed/client/oembedImageWidget.js
new file mode 100644
index 0000000000000000000000000000000000000000..82c71ae1f415e8a529c9dd02f9b65c61c76f682b
--- /dev/null
+++ b/packages/rocketchat-oembed/client/oembedImageWidget.js
@@ -0,0 +1,21 @@
+Template.oembedImageWidget.helpers({
+	loadImage() {
+		const user = Meteor.user();
+
+		if (user && user.settings && user.settings.preferences && user.settings.preferences.autoImageLoad === false && this.downloadImages == null) {
+			return false;
+		}
+		if (Meteor.Device.isPhone() && user && user.settings && user.settings.preferences && user.settings.preferences.saveMobileBandwidth && this.downloadImages == null) {
+			return false;
+		}
+		return true;
+	},
+	collapsed() {
+		if (this.collapsed != null) {
+			return this.collapsed;
+		} else {
+			const user = Meteor.user();
+			return user && user.settings && user.settings.preferences && user.settings.preferences.collapseMediaByDefault === true;
+		}
+	}
+});
diff --git a/packages/rocketchat-oembed/client/oembedSandstormGrain.coffee b/packages/rocketchat-oembed/client/oembedSandstormGrain.coffee
deleted file mode 100644
index fd78824a49be8751a1bdc43da997b2fda0576e56..0000000000000000000000000000000000000000
--- a/packages/rocketchat-oembed/client/oembedSandstormGrain.coffee
+++ /dev/null
@@ -1,17 +0,0 @@
-Template.oembedSandstormGrain.helpers
-	token: ->
-		return @meta.sandstorm.grain.token
-	appTitle: ->
-		return @meta.sandstorm.grain.appTitle.defaultText
-	grainTitle: ->
-		return @meta.sandstorm.grain.grainTitle
-	appIconUrl: ->
-		return @meta.sandstorm.grain.appIconUrl
-	descriptor: ->
-		return @meta.sandstorm.grain.descriptor
-window.sandstormOembed = (e) ->
-	e = e or window.event
-	src = e.target or e.srcElement
-	token = src.getAttribute "data-token"
-	descriptor = src.getAttribute "data-descriptor"
-	Meteor.call "sandstormOffer", token, descriptor
diff --git a/packages/rocketchat-oembed/client/oembedSandstormGrain.js b/packages/rocketchat-oembed/client/oembedSandstormGrain.js
new file mode 100644
index 0000000000000000000000000000000000000000..0b1fbd548fda7b16038f8e4bb5f87fe5544243f8
--- /dev/null
+++ b/packages/rocketchat-oembed/client/oembedSandstormGrain.js
@@ -0,0 +1,25 @@
+Template.oembedSandstormGrain.helpers({
+	token() {
+		return this.meta.sandstorm.grain.token;
+	},
+	appTitle() {
+		return this.meta.sandstorm.grain.appTitle.defaultText;
+	},
+	grainTitle() {
+		return this.meta.sandstorm.grain.grainTitle;
+	},
+	appIconUrl() {
+		return this.meta.sandstorm.grain.appIconUrl;
+	},
+	descriptor() {
+		return this.meta.sandstorm.grain.descriptor;
+	}
+});
+
+window.sandstormOembed = function(e) {
+	e = e || window.event;
+	const src = e.target || e.srcElement;
+	const token = src.getAttribute('data-token');
+	const descriptor = src.getAttribute('data-descriptor');
+	return Meteor.call('sandstormOffer', token, descriptor);
+};
diff --git a/packages/rocketchat-oembed/client/oembedUrlWidget.coffee b/packages/rocketchat-oembed/client/oembedUrlWidget.coffee
deleted file mode 100644
index 5b160cfb235aa38d3e0e1f1ac7fa8d46dfb0d8f9..0000000000000000000000000000000000000000
--- a/packages/rocketchat-oembed/client/oembedUrlWidget.coffee
+++ /dev/null
@@ -1,57 +0,0 @@
-getTitle = (self) ->
-	if not self.meta?
-		return
-
-	return self.meta.ogTitle or self.meta.twitterTitle or self.meta.title or self.meta.pageTitle
-
-getDescription = (self) ->
-	if not self.meta?
-		return
-
-	description = self.meta.ogDescription or self.meta.twitterDescription or self.meta.description
-	if not description?
-		return
-
-	return _.unescape description.replace /(^[“\s]*)|([”\s]*$)/g, ''
-
-
-Template.oembedUrlWidget.helpers
-	description: ->
-		description = getDescription this
-		return Blaze._escape(description) if _.isString description
-
-	title: ->
-		title = getTitle this
-		return Blaze._escape(title) if _.isString title
-
-	target: ->
-		if not this.parsedUrl?.host || !document?.location?.host || this.parsedUrl.host isnt document.location.host
-			return '_blank'
-
-	image: ->
-		if not this.meta?
-			return
-
-		decodedOgImage = @meta.ogImage?.replace?(/&amp;/g, '&')
-
-		url = this.meta.msapplicationTileImage or decodedOgImage or this.meta.twitterImage
-
-		if not url?
-			return
-
-		if url.indexOf('//') is 0
-			url = "#{this.parsedUrl.protocol}#{url}"
-
-		else if url.indexOf('/') is 0 and this.parsedUrl?.host?
-			url = "#{this.parsedUrl.protocol}//#{this.parsedUrl.host}#{url}"
-
-		return url
-
-	show: ->
-		return getDescription(this)? or getTitle(this)?
-
-	collapsed: ->
-		if this.collapsed?
-			return this.collapsed
-		else
-			return Meteor.user()?.settings?.preferences?.collapseMediaByDefault is true
diff --git a/packages/rocketchat-oembed/client/oembedUrlWidget.js b/packages/rocketchat-oembed/client/oembedUrlWidget.js
new file mode 100644
index 0000000000000000000000000000000000000000..110f0c96e70dc6986f1f052bdf84c984b44034d5
--- /dev/null
+++ b/packages/rocketchat-oembed/client/oembedUrlWidget.js
@@ -0,0 +1,67 @@
+const getTitle = function(self) {
+	if (self.meta == null) {
+		return;
+	}
+	return self.meta.ogTitle || self.meta.twitterTitle || self.meta.title || self.meta.pageTitle;
+};
+
+const getDescription = function(self) {
+	if (self.meta == null) {
+		return;
+	}
+	const description = self.meta.ogDescription || self.meta.twitterDescription || self.meta.description;
+	if (description == null) {
+		return;
+	}
+	return _.unescape(description.replace(/(^[“\s]*)|([”\s]*$)/g, ''));
+};
+
+Template.oembedUrlWidget.helpers({
+	description() {
+		const description = getDescription(this);
+		if (_.isString(description)) {
+			return Blaze._escape(description);
+		}
+	},
+	title() {
+		const title = getTitle(this);
+		if (_.isString(title)) {
+			return Blaze._escape(title);
+		}
+	},
+	target() {
+		if (!(this.parsedUrl && this.parsedUrl.host) || !(document && document.location && document.location.host) || (this.parsedUrl && this.parsedUrl.host !== document.location.host)) {
+			return '_blank';
+		}
+	},
+	image() {
+		if (this.meta == null) {
+			return;
+		}
+		let decodedOgImage;
+		if (this.meta.ogImage && this.meta.ogImage.replace) {
+			decodedOgImage = this.meta.ogImage.replace(/&amp;/g, '&');
+		}
+		let url = this.meta.msapplicationTileImage || decodedOgImage || this.meta.twitterImage;
+		if (url == null) {
+			return;
+		}
+		if (url.indexOf('//') === 0) {
+			url = `${ this.parsedUrl.protocol }${ url }`;
+		} else if (url.indexOf('/') === 0 && (this.parsedUrl && this.parsedUrl.host)) {
+			url = `${ this.parsedUrl.protocol }//${ this.parsedUrl.host }${ url }`;
+		}
+		return url;
+	},
+	show() {
+		return (getDescription(this) != null) || (getTitle(this) != null);
+	},
+	collapsed() {
+		if (this.collapsed != null) {
+			return this.collapsed;
+		} else {
+			const user = Meteor.user();
+			return user && user.settings && user.settings.preferences && user.settings.preferences.collapseMediaByDefault === true;
+		}
+	}
+});
diff --git a/packages/rocketchat-oembed/client/oembedVideoWidget.coffee b/packages/rocketchat-oembed/client/oembedVideoWidget.coffee
deleted file mode 100644
index 399e57e57defedf7e3c81d49f8b96d840708b179..0000000000000000000000000000000000000000
--- a/packages/rocketchat-oembed/client/oembedVideoWidget.coffee
+++ /dev/null
@@ -1,22 +0,0 @@
-getTitle = (self) ->
-	if not self.meta?
-		return
-
-	return self.meta.ogTitle or self.meta.twitterTitle or self.meta.title or self.meta.pageTitle
-
-
-Template.oembedVideoWidget.helpers
-	url: ->
-		return @meta?.twitterPlayerStream or @url
-
-	contentType: ->
-		return @meta?.twitterPlayerStreamContentType or @headers?.contentType
-
-	title: ->
-		return getTitle @
-
-	collapsed: ->
-		if this.collapsed?
-			return this.collapsed
-		else
-			return Meteor.user()?.settings?.preferences?.collapseMediaByDefault is true
diff --git a/packages/rocketchat-oembed/client/oembedVideoWidget.js b/packages/rocketchat-oembed/client/oembedVideoWidget.js
new file mode 100644
index 0000000000000000000000000000000000000000..a825ab5c976becac156d2c12a8511597887388b2
--- /dev/null
+++ b/packages/rocketchat-oembed/client/oembedVideoWidget.js
@@ -0,0 +1,35 @@
+const getTitle = function(self) {
+	if (self.meta == null) {
+		return;
+	}
+	return self.meta.ogTitle || self.meta.twitterTitle || self.meta.title || self.meta.pageTitle;
+};
+
+Template.oembedVideoWidget.helpers({
+	url() {
+		if (this.meta && this.meta.twitterPlayerStream) {
+			return this.meta.twitterPlayerStream;
+		} else if (this.url) {
+			return this.url;
+		}
+	},
+	contentType() {
+		if (this.meta && this.meta.twitterPlayerStreamContentType) {
+			return this.meta.twitterPlayerStreamContentType;
+		} else if (this.headers && this.headers.contentType) {
+			return this.headers.contentType;
+		}
+	},
+	title() {
+		return getTitle(this);
+	},
+	collapsed() {
+		if (this.collapsed) {
+			return this.collapsed;
+		} else {
+			const user = Meteor.user();
+			return user && user.settings && user.settings.preferences && user.settings.preferences.collapseMediaByDefault === true;
+		}
+	}
+
+});
diff --git a/packages/rocketchat-oembed/client/oembedYoutubeWidget.coffee b/packages/rocketchat-oembed/client/oembedYoutubeWidget.coffee
deleted file mode 100644
index c848a17f455d561af92d62c73dd98b59547c01c0..0000000000000000000000000000000000000000
--- a/packages/rocketchat-oembed/client/oembedYoutubeWidget.coffee
+++ /dev/null
@@ -1,7 +0,0 @@
-Template.oembedYoutubeWidget.helpers
-
-	collapsed: ->
-		if this.collapsed?
-			return this.collapsed
-		else
-			return Meteor.user()?.settings?.preferences?.collapseMediaByDefault is true
diff --git a/packages/rocketchat-oembed/client/oembedYoutubeWidget.js b/packages/rocketchat-oembed/client/oembedYoutubeWidget.js
new file mode 100644
index 0000000000000000000000000000000000000000..0e2ce33d4e49930feb25caee56c714ba3f8b64d8
--- /dev/null
+++ b/packages/rocketchat-oembed/client/oembedYoutubeWidget.js
@@ -0,0 +1,10 @@
+Template.oembedYoutubeWidget.helpers({
+	collapsed() {
+		if (this.collapsed) {
+			return this.collapsed;
+		} else {
+			const user = Meteor.user();
+			return user && user.settings && user.settings.preferences && user.settings.preferences.collapseMediaByDefault === true;
+		}
+	}
+});
diff --git a/packages/rocketchat-oembed/package.js b/packages/rocketchat-oembed/package.js
index 093b324f689276e437cb0e012f34ce61be228d13..9f776d12a208fd0937fbb2f605876eb9f8eb7cf2 100644
--- a/packages/rocketchat-oembed/package.js
+++ b/packages/rocketchat-oembed/package.js
@@ -17,40 +17,39 @@ Package.onUse(function(api) {
 		'http',
 		'templating',
 		'ecmascript',
-		'coffeescript',
 		'underscore',
 		'konecty:change-case',
 		'rocketchat:lib'
 	]);
 
 	api.addFiles('client/baseWidget.html', 'client');
-	api.addFiles('client/baseWidget.coffee', 'client');
+	api.addFiles('client/baseWidget.js', 'client');
 
 	api.addFiles('client/oembedImageWidget.html', 'client');
-	api.addFiles('client/oembedImageWidget.coffee', 'client');
+	api.addFiles('client/oembedImageWidget.js', 'client');
 
 	api.addFiles('client/oembedAudioWidget.html', 'client');
-	api.addFiles('client/oembedAudioWidget.coffee', 'client');
+	api.addFiles('client/oembedAudioWidget.js', 'client');
 
 	api.addFiles('client/oembedVideoWidget.html', 'client');
-	api.addFiles('client/oembedVideoWidget.coffee', 'client');
+	api.addFiles('client/oembedVideoWidget.js', 'client');
 
 	api.addFiles('client/oembedYoutubeWidget.html', 'client');
-	api.addFiles('client/oembedYoutubeWidget.coffee', 'client');
+	api.addFiles('client/oembedYoutubeWidget.js', 'client');
 
 	api.addFiles('client/oembedUrlWidget.html', 'client');
-	api.addFiles('client/oembedUrlWidget.coffee', 'client');
+	api.addFiles('client/oembedUrlWidget.js', 'client');
 
 	api.addFiles('client/oembedFrameWidget.html', 'client');
-	api.addFiles('client/oembedFrameWidget.coffee', 'client');
+	api.addFiles('client/oembedFrameWidget.js', 'client');
 
 	api.addFiles('client/oembedSandstormGrain.html', 'client');
-	api.addFiles('client/oembedSandstormGrain.coffee', 'client');
+	api.addFiles('client/oembedSandstormGrain.js', 'client');
 
-	api.addFiles('server/server.coffee', 'server');
-	api.addFiles('server/providers.coffee', 'server');
+	api.addFiles('server/server.js', 'server');
+	api.addFiles('server/providers.js', 'server');
 	api.addFiles('server/jumpToMessage.js', 'server');
-	api.addFiles('server/models/OEmbedCache.coffee', 'server');
+	api.addFiles('server/models/OEmbedCache.js', 'server');
 
 	api.export('OEmbed', 'server');
 });
diff --git a/packages/rocketchat-oembed/server/jumpToMessage.js b/packages/rocketchat-oembed/server/jumpToMessage.js
index 0f674e461bfac4e01faf6421c23e88aabd910da0..2cb673cb3a9b32a1dc51713f9a0a26dbb656e67e 100644
--- a/packages/rocketchat-oembed/server/jumpToMessage.js
+++ b/packages/rocketchat-oembed/server/jumpToMessage.js
@@ -17,7 +17,7 @@ RocketChat.callbacks.add('beforeSaveMessage', (msg) => {
 							msg.attachments.push({
 								'text' : jumpToMessage.msg,
 								'translations': jumpToMessage.translations,
-								'author_name' : jumpToMessage.u.username,
+								'author_name' : jumpToMessage.alias || jumpToMessage.u.username,
 								'author_icon' : getAvatarUrlFromUsername(jumpToMessage.u.username),
 								'message_link' : item.url,
 								'attachments' : jumpToMessage.attachments || [],
diff --git a/packages/rocketchat-oembed/server/models/OEmbedCache.coffee b/packages/rocketchat-oembed/server/models/OEmbedCache.coffee
deleted file mode 100644
index cdf8fbfbf27f6908485b6a500b34cadba1a4f299..0000000000000000000000000000000000000000
--- a/packages/rocketchat-oembed/server/models/OEmbedCache.coffee
+++ /dev/null
@@ -1,30 +0,0 @@
-RocketChat.models.OEmbedCache = new class extends RocketChat.models._Base
-	constructor: ->
-		super('oembed_cache')
-		@tryEnsureIndex { 'updatedAt': 1 }
-
-
-	# FIND ONE
-	findOneById: (_id, options) ->
-		query =
-			_id: _id
-
-		return @findOne query, options
-
-
-	# INSERT
-	createWithIdAndData: (_id, data) ->
-		record =
-			_id: _id
-			data: data
-			updatedAt: new Date
-
-		record._id = @insert record
-		return record
-
-	# REMOVE
-	removeAfterDate: (date) ->
-		query =
-			updatedAt:
-				$lte: date
-		@remove query
diff --git a/packages/rocketchat-oembed/server/models/OEmbedCache.js b/packages/rocketchat-oembed/server/models/OEmbedCache.js
new file mode 100644
index 0000000000000000000000000000000000000000..f0b451247f9e02f007b614c8f69c87dac61364ad
--- /dev/null
+++ b/packages/rocketchat-oembed/server/models/OEmbedCache.js
@@ -0,0 +1,38 @@
+
+RocketChat.models.OEmbedCache = new class extends RocketChat.models._Base {
+	constructor() {
+		super('oembed_cache');
+		this.tryEnsureIndex({ 'updatedAt': 1 });
+	}
+
+    //FIND ONE
+	findOneById(_id, options) {
+		const query = {
+			_id
+		};
+		return this.findOne(query, options);
+	}
+
+    //INSERT
+	createWithIdAndData(_id, data) {
+		const record = {
+			_id,
+			data,
+			updatedAt: new Date
+		};
+		record._id = this.insert(record);
+		return record;
+	}
+
+    //REMOVE
+	removeAfterDate(date) {
+		const query = {
+			updatedAt: {
+				$lte: date
+			}
+		};
+		return this.remove(query);
+	}
+};
+
+
diff --git a/packages/rocketchat-oembed/server/providers.coffee b/packages/rocketchat-oembed/server/providers.coffee
deleted file mode 100644
index 2a4b3002e1b7e8b377becd07a3cb5211fa71e20f..0000000000000000000000000000000000000000
--- a/packages/rocketchat-oembed/server/providers.coffee
+++ /dev/null
@@ -1,84 +0,0 @@
-URL = Npm.require('url')
-QueryString = Npm.require('querystring')
-
-class Providers
-	providers: []
-
-	@getConsumerUrl: (provider, url) ->
-		urlObj = URL.parse provider.endPoint, true
-		urlObj.query['url'] = url
-		delete urlObj.search
-		return URL.format urlObj
-
-	registerProvider: (provider) ->
-		this.providers.push(provider)
-
-	getProviders: () ->
-		return this.providers
-
-	getProviderForUrl: (url) ->
-		return _.find this.providers, (provider) ->
-			candidate = _.find provider.urls, (re) ->
-				return re.test url
-			return candidate?
-
-providers = new Providers()
-providers.registerProvider
-	urls: [new RegExp('https?://soundcloud.com/\\S+')]
-	endPoint: 'https://soundcloud.com/oembed?format=json&maxheight=150'
-providers.registerProvider
-	urls: [new RegExp('https?://vimeo.com/[^/]+'), new RegExp('https?://vimeo.com/channels/[^/]+/[^/]+'), new RegExp('https://vimeo.com/groups/[^/]+/videos/[^/]+')]
-	endPoint: 'https://vimeo.com/api/oembed.json?maxheight=200'
-providers.registerProvider
-	urls: [new RegExp('https?://www.youtube.com/\\S+'), new RegExp('https?://youtu.be/\\S+')]
-	endPoint: 'https://www.youtube.com/oembed?maxheight=200'
-providers.registerProvider
-	urls: [new RegExp('https?://www.rdio.com/\\S+'), new RegExp('https?://rd.io/\\S+')]
-	endPoint: 'https://www.rdio.com/api/oembed/?format=json&maxheight=150'
-providers.registerProvider
-	urls: [new RegExp('https?://www.slideshare.net/[^/]+/[^/]+')]
-	endPoint: 'https://www.slideshare.net/api/oembed/2?format=json&maxheight=200'
-providers.registerProvider
-	urls: [new RegExp('https?://www.dailymotion.com/video/\\S+')]
-	endPoint: 'https://www.dailymotion.com/services/oembed?maxheight=200'
-
-RocketChat.oembed = {}
-RocketChat.oembed.providers = providers
-
-RocketChat.callbacks.add 'oembed:beforeGetUrlContent', (data) ->
-	if data.parsedUrl?
-		url = URL.format data.parsedUrl
-		provider = providers.getProviderForUrl url
-		if provider?
-			consumerUrl = Providers.getConsumerUrl provider, url
-			consumerUrl = URL.parse consumerUrl, true
-			_.extend data.parsedUrl, consumerUrl
-			data.urlObj.port = consumerUrl.port
-			data.urlObj.hostname = consumerUrl.hostname
-			data.urlObj.pathname = consumerUrl.pathname
-			data.urlObj.query = consumerUrl.query
-			delete data.urlObj.search
-			delete data.urlObj.host
-
-	return data
-, RocketChat.callbacks.priority.MEDIUM, 'oembed-providers-before'
-
-RocketChat.callbacks.add 'oembed:afterParseContent', (data) ->
-	if data.parsedUrl?.query?
-		queryString = data.parsedUrl.query
-		if _.isString data.parsedUrl.query
-			queryString = QueryString.parse data.parsedUrl.query
-		if queryString.url?
-			url = queryString.url
-			provider = providers.getProviderForUrl url
-			if provider?
-				if data.content?.body?
-					try
-						metas = JSON.parse data.content.body;
-						_.each metas, (value, key) ->
-							if _.isString value
-								data.meta[changeCase.camelCase('oembed_' + key)] = value
-						data.meta['oembedUrl'] = url
-
-	return data
-, RocketChat.callbacks.priority.MEDIUM, 'oembed-providers-after'
diff --git a/packages/rocketchat-oembed/server/providers.js b/packages/rocketchat-oembed/server/providers.js
new file mode 100644
index 0000000000000000000000000000000000000000..4d5c59ffce9ac52af2e866572f202c5b175e2740
--- /dev/null
+++ b/packages/rocketchat-oembed/server/providers.js
@@ -0,0 +1,120 @@
+/*globals changeCase */
+
+
+const URL = Npm.require('url');
+
+const QueryString = Npm.require('querystring');
+
+class Providers {
+	constructor() {
+		this.providers = [];
+	}
+
+	static getConsumerUrl(provider, url) {
+		const urlObj = URL.parse(provider.endPoint, true);
+		urlObj.query['url'] = url;
+		delete urlObj.search;
+		return URL.format(urlObj);
+	}
+
+	registerProvider(provider) {
+		return this.providers.push(provider);
+	}
+
+	getProviders() {
+		return this.providers;
+	}
+
+	getProviderForUrl(url) {
+		return _.find(this.providers, function(provider) {
+			const candidate = _.find(provider.urls, function(re) {
+				return re.test(url);
+			});
+			return candidate != null;
+		});
+	}
+}
+
+const providers = new Providers();
+
+providers.registerProvider({
+	urls: [new RegExp('https?://soundcloud.com/\\S+')],
+	endPoint: 'https://soundcloud.com/oembed?format=json&maxheight=150'
+});
+
+providers.registerProvider({
+	urls: [new RegExp('https?://vimeo.com/[^/]+'), new RegExp('https?://vimeo.com/channels/[^/]+/[^/]+'), new RegExp('https://vimeo.com/groups/[^/]+/videos/[^/]+')],
+	endPoint: 'https://vimeo.com/api/oembed.json?maxheight=200'
+});
+
+providers.registerProvider({
+	urls: [new RegExp('https?://www.youtube.com/\\S+'), new RegExp('https?://youtu.be/\\S+')],
+	endPoint: 'https://www.youtube.com/oembed?maxheight=200'
+});
+
+providers.registerProvider({
+	urls: [new RegExp('https?://www.rdio.com/\\S+'), new RegExp('https?://rd.io/\\S+')],
+	endPoint: 'https://www.rdio.com/api/oembed/?format=json&maxheight=150'
+});
+
+providers.registerProvider({
+	urls: [new RegExp('https?://www.slideshare.net/[^/]+/[^/]+')],
+	endPoint: 'https://www.slideshare.net/api/oembed/2?format=json&maxheight=200'
+});
+
+providers.registerProvider({
+	urls: [new RegExp('https?://www.dailymotion.com/video/\\S+')],
+	endPoint: 'https://www.dailymotion.com/services/oembed?maxheight=200'
+});
+
+RocketChat.oembed = {};
+
+RocketChat.oembed.providers = providers;
+
+RocketChat.callbacks.add('oembed:beforeGetUrlContent', function(data) {
+	if (data.parsedUrl != null) {
+		const url = URL.format(data.parsedUrl);
+		const provider = providers.getProviderForUrl(url);
+		if (provider != null) {
+			let consumerUrl = Providers.getConsumerUrl(provider, url);
+			consumerUrl = URL.parse(consumerUrl, true);
+			_.extend(data.parsedUrl, consumerUrl);
+			data.urlObj.port = consumerUrl.port;
+			data.urlObj.hostname = consumerUrl.hostname;
+			data.urlObj.pathname = consumerUrl.pathname;
+			data.urlObj.query = consumerUrl.query;
+			delete data.urlObj.search;
+			delete data.urlObj.host;
+		}
+	}
+	return data;
+}, RocketChat.callbacks.priority.MEDIUM, 'oembed-providers-before');
+
+RocketChat.callbacks.add('oembed:afterParseContent', function(data) {
+	if (data.parsedUrl && data.parsedUrl.query) {
+		let queryString = data.parsedUrl.query;
+		if (_.isString(data.parsedUrl.query)) {
+			queryString = QueryString.parse(data.parsedUrl.query);
+		}
+		if (queryString.url != null) {
+			const url = queryString.url;
+			const provider = providers.getProviderForUrl(url);
+			if (provider != null) {
+				if (data.content && data.content.body) {
+					try {
+						const metas = JSON.parse(data.content.body);
+						_.each(metas, function(value, key) {
+							if (_.isString(value)) {
+								return data.meta[changeCase.camelCase(`oembed_${ key }`)] = value;
+							}
+						});
+						data.meta['oembedUrl'] = url;
+					} catch (error) {
+						console.log(error);
+					}
+				}
+			}
+		}
+	}
+	return data;
+}, RocketChat.callbacks.priority.MEDIUM, 'oembed-providers-after');
diff --git a/packages/rocketchat-oembed/server/server.coffee b/packages/rocketchat-oembed/server/server.coffee
deleted file mode 100644
index 63ab940c3d55066bd09dec74a182843f222772af..0000000000000000000000000000000000000000
--- a/packages/rocketchat-oembed/server/server.coffee
+++ /dev/null
@@ -1,256 +0,0 @@
-URL = Npm.require('url')
-querystring = Npm.require('querystring')
-request = HTTPInternals.NpmModules.request.module
-iconv = Npm.require('iconv-lite')
-ipRangeCheck = Npm.require('ip-range-check')
-he = Npm.require('he')
-jschardet = Npm.require('jschardet')
-
-OEmbed = {}
-
-# Detect encoding
-# Priority:
-#   Detected == HTTP Header > Detected == HTML meta > HTTP Header > HTML meta > Detected > Default (utf-8)
-#   See also: https://www.w3.org/International/questions/qa-html-encoding-declarations.en#quickanswer
-getCharset = (contentType, body) ->
-	contentType = contentType || ''
-	binary = body.toString('binary')
-
-	detected = jschardet.detect(binary)
-	if detected.confidence > 0.8
-		detectedCharset = detected.encoding.toLowerCase()
-
-	m1 = contentType.match(/charset=([\w\-]+)/i)
-	if m1
-		httpHeaderCharset = m1[1].toLowerCase()
-
-	m2 = binary.match(/<meta\b[^>]*charset=["']?([\w\-]+)/i)
-	if m2
-		htmlMetaCharset = m2[1].toLowerCase()
-
-	if detectedCharset
-		if detectedCharset == httpHeaderCharset
-			result = httpHeaderCharset
-		else if detectedCharset == htmlMetaCharset
-			result = htmlMetaCharset
-
-	unless result
-		result = httpHeaderCharset || htmlMetaCharset || detectedCharset
-
-	return result || 'utf-8'
-
-toUtf8 = (contentType, body) ->
-	return iconv.decode(body, getCharset(contentType, body))
-
-getUrlContent = (urlObj, redirectCount = 5, callback) ->
-	if _.isString(urlObj)
-		urlObj = URL.parse urlObj
-
-	parsedUrl = _.pick urlObj, ['host', 'hash', 'pathname', 'protocol', 'port', 'query', 'search', 'hostname']
-
-	ignoredHosts = RocketChat.settings.get('API_EmbedIgnoredHosts').replace(/\s/g, '').split(',') or []
-	if parsedUrl.hostname in ignoredHosts or ipRangeCheck(parsedUrl.hostname, ignoredHosts)
-		return callback()
-
-	safePorts = RocketChat.settings.get('API_EmbedSafePorts').replace(/\s/g, '').split(',') or []
-	if parsedUrl.port and safePorts.length > 0 and parsedUrl.port not in safePorts
-		return callback()
-
-	data = RocketChat.callbacks.run 'oembed:beforeGetUrlContent',
-		urlObj: urlObj
-		parsedUrl: parsedUrl
-
-	if data.attachments?
-		return callback null, data
-
-	url = URL.format data.urlObj
-	opts =
-		url: url
-		strictSSL: !RocketChat.settings.get 'Allow_Invalid_SelfSigned_Certs'
-		gzip: true
-		maxRedirects: redirectCount
-		headers:
-			'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36'
-
-	headers = null
-	statusCode = null
-	error = null
-	chunks = []
-	chunksTotalLength = 0
-
-	stream = request opts
-	stream.on 'response', (response) ->
-		statusCode = response.statusCode
-		headers = response.headers
-		if response.statusCode isnt 200
-			return stream.abort()
-
-	stream.on 'data', (chunk) ->
-		chunks.push chunk
-		chunksTotalLength += chunk.length
-		if chunksTotalLength > 250000
-			stream.abort()
-
-	stream.on 'end', Meteor.bindEnvironment ->
-		if error?
-			return callback null, {
-				error: error
-				parsedUrl: parsedUrl
-			}
-
-		buffer = Buffer.concat(chunks)
-
-		callback null, {
-			headers: headers
-			body: toUtf8(headers['content-type'], buffer)
-			parsedUrl: parsedUrl
-			statusCode: statusCode
-		}
-
-	stream.on 'error', (err) ->
-		error = err
-
-OEmbed.getUrlMeta = (url, withFragment) ->
-	getUrlContentSync = Meteor.wrapAsync getUrlContent
-
-	urlObj = URL.parse url
-
-	if withFragment?
-		queryStringObj = querystring.parse urlObj.query
-		queryStringObj._escaped_fragment_ = ''
-		urlObj.query = querystring.stringify queryStringObj
-
-		path = urlObj.pathname
-		if urlObj.query?
-			path += '?' + urlObj.query
-
-		urlObj.path = path
-
-	content = getUrlContentSync urlObj, 5
-	if !content
-		return
-
-	if content.attachments?
-		return content
-
-	metas = undefined
-
-	if content?.body?
-		metas = {}
-		content.body.replace /<title[^>]*>([^<]*)<\/title>/gmi, (meta, title) ->
-			metas.pageTitle ?= he.unescape title
-
-		content.body.replace /<meta[^>]*(?:name|property)=[']([^']*)['][^>]*\scontent=[']([^']*)['][^>]*>/gmi, (meta, name, value) ->
-			metas[changeCase.camelCase(name)] ?= he.unescape value
-
-		content.body.replace /<meta[^>]*(?:name|property)=["]([^"]*)["][^>]*\scontent=["]([^"]*)["][^>]*>/gmi, (meta, name, value) ->
-			metas[changeCase.camelCase(name)] ?= he.unescape value
-
-		content.body.replace /<meta[^>]*\scontent=[']([^']*)['][^>]*(?:name|property)=[']([^']*)['][^>]*>/gmi, (meta, value, name) ->
-			metas[changeCase.camelCase(name)] ?= he.unescape value
-
-		content.body.replace /<meta[^>]*\scontent=["]([^"]*)["][^>]*(?:name|property)=["]([^"]*)["][^>]*>/gmi, (meta, value, name) ->
-			metas[changeCase.camelCase(name)] ?= he.unescape value
-
-
-		if metas.fragment is '!' and not withFragment?
-			return OEmbed.getUrlMeta url, true
-
-	headers = undefined
-
-	if content?.headers?
-		headers = {}
-		for header, value of content.headers
-			headers[changeCase.camelCase(header)] = value
-
-	if content?.statusCode isnt 200
-		return data
-
-	data = RocketChat.callbacks.run 'oembed:afterParseContent',
-		meta: metas
-		headers: headers
-		parsedUrl: content.parsedUrl
-		content: content
-
-	return data
-
-OEmbed.getUrlMetaWithCache = (url, withFragment) ->
-	cache = RocketChat.models.OEmbedCache.findOneById url
-	if cache?
-		return cache.data
-
-	data = OEmbed.getUrlMeta url, withFragment
-
-	if data?
-		try
-			RocketChat.models.OEmbedCache.createWithIdAndData url, data
-		catch e
-			console.error 'OEmbed duplicated record', url
-
-		return data
-
-	return
-
-getRelevantHeaders = (headersObj) ->
-	headers = {}
-	for key, value of headersObj
-		if key.toLowerCase() in ['contenttype', 'contentlength'] and value?.trim() isnt ''
-			headers[key] = value
-
-	if Object.keys(headers).length > 0
-		return headers
-	return
-
-getRelevantMetaTags = (metaObj) ->
-	tags = {}
-	for key, value of metaObj
-		if /^(og|fb|twitter|oembed|msapplication).+|description|title|pageTitle$/.test(key.toLowerCase()) and value?.trim() isnt ''
-			tags[key] = value
-
-	if Object.keys(tags).length > 0
-		return tags
-	return
-
-OEmbed.rocketUrlParser = (message) ->
-	if Array.isArray message.urls
-		attachments = []
-		changed = false
-		message.urls.forEach (item) ->
-			if item.ignoreParse is true then return
-			if item.url.startsWith "grain://"
-				changed = true
-				item.meta =
-					sandstorm:
-						grain: item.sandstormViewInfo
-				return
-
-			if not /^https?:\/\//i.test item.url then return
-
-			data = OEmbed.getUrlMetaWithCache item.url
-
-			if data?
-				if data.attachments
-					attachments = _.union attachments, data.attachments
-				else
-					if data.meta?
-						item.meta = getRelevantMetaTags data.meta
-
-					if data.headers?
-						item.headers = getRelevantHeaders data.headers
-
-					item.parsedUrl = data.parsedUrl
-					changed = true
-
-		if attachments.length
-			RocketChat.models.Messages.setMessageAttachments message._id, attachments
-
-		if changed is true
-			RocketChat.models.Messages.setUrlsById message._id, message.urls
-
-	return message
-
-RocketChat.settings.get 'API_Embed', (key, value) ->
-	if value
-		RocketChat.callbacks.add 'afterSaveMessage', OEmbed.rocketUrlParser, RocketChat.callbacks.priority.LOW, 'API_Embed'
-	else
-		RocketChat.callbacks.remove 'afterSaveMessage', 'API_Embed'
diff --git a/packages/rocketchat-oembed/server/server.js b/packages/rocketchat-oembed/server/server.js
new file mode 100644
index 0000000000000000000000000000000000000000..36085f1597f7efd1a42b1e83334ba4985402b15d
--- /dev/null
+++ b/packages/rocketchat-oembed/server/server.js
@@ -0,0 +1,300 @@
+/*globals HTTPInternals, changeCase */
+const URL = Npm.require('url');
+
+const querystring = Npm.require('querystring');
+
+const request = HTTPInternals.NpmModules.request.module;
+
+const iconv = Npm.require('iconv-lite');
+
+const ipRangeCheck = Npm.require('ip-range-check');
+
+const he = Npm.require('he');
+
+const jschardet = Npm.require('jschardet');
+
+const OEmbed = {};
+
+//  Detect encoding
+//  Priority:
+//  Detected == HTTP Header > Detected == HTML meta > HTTP Header > HTML meta > Detected > Default (utf-8)
+//  See also: https://www.w3.org/International/questions/qa-html-encoding-declarations.en#quickanswer
+const getCharset = function(contentType, body) {
+	let detectedCharset;
+	let httpHeaderCharset;
+	let htmlMetaCharset;
+	let result;
+
+	contentType = contentType || '';
+
+	const binary = body.toString('binary');
+	const detected = jschardet.detect(binary);
+	if (detected.confidence > 0.8) {
+		detectedCharset = detected.encoding.toLowerCase();
+	}
+	const m1 = contentType.match(/charset=([\w\-]+)/i);
+	if (m1) {
+		httpHeaderCharset = m1[1].toLowerCase();
+	}
+	const m2 = binary.match(/<meta\b[^>]*charset=["']?([\w\-]+)/i);
+	if (m2) {
+		htmlMetaCharset = m2[1].toLowerCase();
+	}
+	if (detectedCharset) {
+		if (detectedCharset === httpHeaderCharset) {
+			result = httpHeaderCharset;
+		} else if (detectedCharset === htmlMetaCharset) {
+			result = htmlMetaCharset;
+		}
+	}
+	if (!result) {
+		result = httpHeaderCharset || htmlMetaCharset || detectedCharset;
+	}
+	return result || 'utf-8';
+};
+
+const toUtf8 = function(contentType, body) {
+	return iconv.decode(body, getCharset(contentType, body));
+};
+
+const getUrlContent = function(urlObj, redirectCount = 5, callback) {
+
+	if (_.isString(urlObj)) {
+		urlObj = URL.parse(urlObj);
+	}
+
+	const parsedUrl = _.pick(urlObj, ['host', 'hash', 'pathname', 'protocol', 'port', 'query', 'search', 'hostname']);
+	const ignoredHosts = RocketChat.settings.get('API_EmbedIgnoredHosts').replace(/\s/g, '').split(',') || [];
+	if (ignoredHosts.includes(parsedUrl.hostname) || ipRangeCheck(parsedUrl.hostname, ignoredHosts)) {
+		return callback();
+	}
+
+	const safePorts = RocketChat.settings.get('API_EmbedSafePorts').replace(/\s/g, '').split(',') || [];
+	if (parsedUrl.port && safePorts.length > 0 && (!safePorts.includes(parsedUrl.port))) {
+		return callback();
+	}
+
+	const data = RocketChat.callbacks.run('oembed:beforeGetUrlContent', {
+		urlObj,
+		parsedUrl
+	});
+	if (data.attachments != null) {
+		return callback(null, data);
+	}
+	const url = URL.format(data.urlObj);
+	const opts = {
+		url,
+		strictSSL: !RocketChat.settings.get('Allow_Invalid_SelfSigned_Certs'),
+		gzip: true,
+		maxRedirects: redirectCount,
+		headers: {
+			'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36'
+		}
+	};
+	let headers = null;
+	let statusCode = null;
+	let error = null;
+	const chunks = [];
+	let chunksTotalLength = 0;
+	const stream = request(opts);
+	stream.on('response', function(response) {
+		statusCode = response.statusCode;
+		headers = response.headers;
+		if (response.statusCode !== 200) {
+			return stream.abort();
+		}
+	});
+	stream.on('data', function(chunk) {
+		chunks.push(chunk);
+		chunksTotalLength += chunk.length;
+		if (chunksTotalLength > 250000) {
+			return stream.abort();
+		}
+	});
+	stream.on('end', Meteor.bindEnvironment(function() {
+		if (error != null) {
+			return callback(null, {
+				error,
+				parsedUrl
+			});
+		}
+		const buffer = Buffer.concat(chunks);
+		return callback(null, {
+			headers,
+			body: toUtf8(headers['content-type'], buffer),
+			parsedUrl,
+			statusCode
+		});
+	}));
+	return stream.on('error', function(err) {
+		return error = err;
+	});
+};
+
+OEmbed.getUrlMeta = function(url, withFragment) {
+	const getUrlContentSync = Meteor.wrapAsync(getUrlContent);
+	const urlObj = URL.parse(url);
+	if (withFragment != null) {
+		const queryStringObj = querystring.parse(urlObj.query);
+		queryStringObj._escaped_fragment_ = '';
+		urlObj.query = querystring.stringify(queryStringObj);
+		let path = urlObj.pathname;
+		if (urlObj.query != null) {
+			path += `?${ urlObj.query }`;
+		}
+		urlObj.path = path;
+	}
+	const content = getUrlContentSync(urlObj, 5);
+	if (!content) {
+		return;
+	}
+	if (content.attachments != null) {
+		return content;
+	}
+	let metas = undefined;
+	if (content && content.body) {
+		metas = {};
+		content.body.replace(/<title[^>]*>([^<]*)<\/title>/gmi, function(meta, title) {
+			return metas.pageTitle != null ? metas.pageTitle : metas.pageTitle = he.unescape(title);
+		});
+		content.body.replace(/<meta[^>]*(?:name|property)=[']([^']*)['][^>]*\scontent=[']([^']*)['][^>]*>/gmi, function(meta, name, value) {
+			let name1;
+			return metas[name1 = changeCase.camelCase(name)] != null ? metas[name1] : metas[name1] = he.unescape(value);
+		});
+		content.body.replace(/<meta[^>]*(?:name|property)=["]([^"]*)["][^>]*\scontent=["]([^"]*)["][^>]*>/gmi, function(meta, name, value) {
+			let name1;
+			return metas[name1 = changeCase.camelCase(name)] != null ? metas[name1] : metas[name1] = he.unescape(value);
+		});
+		content.body.replace(/<meta[^>]*\scontent=[']([^']*)['][^>]*(?:name|property)=[']([^']*)['][^>]*>/gmi, function(meta, value, name) {
+			let name1;
+			return metas[name1 = changeCase.camelCase(name)] != null ? metas[name1] : metas[name1] = he.unescape(value);
+		});
+		content.body.replace(/<meta[^>]*\scontent=["]([^"]*)["][^>]*(?:name|property)=["]([^"]*)["][^>]*>/gmi, function(meta, value, name) {
+			let name1;
+			return metas[name1 = changeCase.camelCase(name)] != null ? metas[name1] : metas[name1] = he.unescape(value);
+		});
+		if (metas.fragment === '!' && (withFragment == null)) {
+			return OEmbed.getUrlMeta(url, true);
+		}
+	}
+	let headers = undefined;
+	let data = undefined;
+
+
+	if (content && content.headers) {
+		headers = {};
+		const headerObj = content.headers;
+		Object.keys(headerObj).forEach((header) => {
+			headers[changeCase.camelCase(header)] = headerObj[header];
+		});
+	}
+	if (content && content.statusCode !== 200) {
+		return data;
+	}
+	data = RocketChat.callbacks.run('oembed:afterParseContent', {
+		meta: metas,
+		headers,
+		parsedUrl: content.parsedUrl,
+		content
+	});
+	return data;
+};
+
+OEmbed.getUrlMetaWithCache = function(url, withFragment) {
+	const cache = RocketChat.models.OEmbedCache.findOneById(url);
+	if (cache != null) {
+		return cache.data;
+	}
+	const data = OEmbed.getUrlMeta(url, withFragment);
+	if (data != null) {
+		try {
+			RocketChat.models.OEmbedCache.createWithIdAndData(url, data);
+		} catch (_error) {
+			console.error('OEmbed duplicated record', url);
+		}
+		return data;
+	}
+};
+
+const getRelevantHeaders = function(headersObj) {
+	const headers = {};
+	Object.keys(headersObj).forEach((key) => {
+		const value = headersObj[key];
+		const lowerCaseKey = key.toLowerCase();
+		if ((lowerCaseKey === 'contenttype' || lowerCaseKey === 'contentlength') && (value && value.trim() !== '')) {
+			headers[key] = value;
+		}
+	});
+
+	if (Object.keys(headers).length > 0) {
+		return headers;
+	}
+};
+
+const getRelevantMetaTags = function(metaObj) {
+	const tags = {};
+	Object.keys(metaObj).forEach((key) => {
+		const value = metaObj[key];
+		if (/^(og|fb|twitter|oembed|msapplication).+|description|title|pageTitle$/.test(key.toLowerCase()) && (value && value.trim() !== '')) {
+			tags[key] = value;
+		}
+	});
+
+	if (Object.keys(tags).length > 0) {
+		return tags;
+	}
+};
+
+OEmbed.rocketUrlParser = function(message) {
+	if (Array.isArray(message.urls)) {
+		let attachments = [];
+		let changed = false;
+		message.urls.forEach(function(item) {
+			if (item.ignoreParse === true) {
+				return;
+			}
+			if (item.url.startsWith('grain://')) {
+				changed = true;
+				item.meta = {
+					sandstorm: {
+						grain: item.sandstormViewInfo
+					}
+				};
+				return;
+			}
+			if (!/^https?:\/\//i.test(item.url)) {
+				return;
+			}
+			const data = OEmbed.getUrlMetaWithCache(item.url);
+			if (data != null) {
+				if (data.attachments) {
+					return attachments = _.union(attachments, data.attachments);
+				} else {
+					if (data.meta != null) {
+						item.meta = getRelevantMetaTags(data.meta);
+					}
+					if (data.headers != null) {
+						item.headers = getRelevantHeaders(data.headers);
+					}
+					item.parsedUrl = data.parsedUrl;
+					return changed = true;
+				}
+			}
+		});
+		if (attachments.length) {
+			RocketChat.models.Messages.setMessageAttachments(message._id, attachments);
+		}
+		if (changed === true) {
+			RocketChat.models.Messages.setUrlsById(message._id, message.urls);
+		}
+	}
+	return message;
+};
+
+RocketChat.settings.get('API_Embed', function(key, value) {
+	if (value) {
+		return RocketChat.callbacks.add('afterSaveMessage', OEmbed.rocketUrlParser, RocketChat.callbacks.priority.LOW, 'API_Embed');
+	} else {
+		return RocketChat.callbacks.remove('afterSaveMessage', 'API_Embed');
+	}
+});
diff --git a/packages/rocketchat-slackbridge/slackbridge.js b/packages/rocketchat-slackbridge/slackbridge.js
index 2dd320a735f2cf7cac5cd94ff66031f1797f00c1..b4bfa1686deb599108879dcbe182eea77856bb3a 100644
--- a/packages/rocketchat-slackbridge/slackbridge.js
+++ b/packages/rocketchat-slackbridge/slackbridge.js
@@ -80,8 +80,9 @@ class SlackBridge {
 		if (!_.isEmpty(slackMsgTxt)) {
 			slackMsgTxt = slackMsgTxt.replace(/<!everyone>/g, '@all');
 			slackMsgTxt = slackMsgTxt.replace(/<!channel>/g, '@all');
-			slackMsgTxt = slackMsgTxt.replace(/&gt;/g, '<');
-			slackMsgTxt = slackMsgTxt.replace(/&lt;/g, '>');
+			slackMsgTxt = slackMsgTxt.replace(/<!here>/g, '@here');
+			slackMsgTxt = slackMsgTxt.replace(/&gt;/g, '>');
+			slackMsgTxt = slackMsgTxt.replace(/&lt;/g, '<');
 			slackMsgTxt = slackMsgTxt.replace(/&amp;/g, '&');
 			slackMsgTxt = slackMsgTxt.replace(/:simple_smile:/g, ':smile:');
 			slackMsgTxt = slackMsgTxt.replace(/:memo:/g, ':pencil:');
@@ -594,7 +595,6 @@ class SlackBridge {
 			const fileId = Meteor.fileStore.create(details);
 			if (fileId) {
 				Meteor.fileStore.write(stream, fileId, (err, file) => {
-					console.log('fileStore.write', file);
 					if (err) {
 						throw new Error(err);
 					} else {
diff --git a/packages/rocketchat-theme/client/imports/base.less b/packages/rocketchat-theme/client/imports/base.less
index b6c1c363391881cba1418190784327394f966294..8340766472acf14937af5cb6208a00cfa47ef90e 100644
--- a/packages/rocketchat-theme/client/imports/base.less
+++ b/packages/rocketchat-theme/client/imports/base.less
@@ -1302,7 +1302,7 @@ label.required::after {
 		&.toggle {
 			font-size: 0;
 
-			> span {
+			> label {
 				display: inline-block;
 				width: calc(~"100% - 40px");
 				font-size: 14px;
@@ -3054,6 +3054,7 @@ label.required::after {
 				max-height: 200px;
 				max-width: 100%;
 				opacity: 0;
+				cursor: pointer;
 			}
 		}
 
@@ -4996,7 +4997,7 @@ a + br.only-after-a {
 				border-width: 0;
 			}
 
-			.stream-info {
+			.users-typing {
 				display: none;
 			}
 
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-admin/client/users/adminUsers.html b/packages/rocketchat-ui-admin/client/users/adminUsers.html
index bf7827daadfb322ef84868aad08e9a253838842a..46e17936f746a749fb7b3898226e5a855316bb94 100644
--- a/packages/rocketchat-ui-admin/client/users/adminUsers.html
+++ b/packages/rocketchat-ui-admin/client/users/adminUsers.html
@@ -29,6 +29,8 @@
 									<th class="content-background-color border-component-color" width="34%">{{_ "Name"}}</th>
 									<th class="content-background-color border-component-color" width="33%">{{_ "Username"}}</th>
 									<th class="content-background-color border-component-color" width="33%">{{_ "Email"}}</th>
+									<th class="content-background-color border-component-color" width="33%">{{_ "Roles"}}</th>
+									<th class="content-background-color border-component-color" width="33%">{{_ "Status"}}</th>
 								</tr>
 							</thead>
 							<tbody>
@@ -42,10 +44,13 @@
 									<td class="border-component-color">{{name}}</td>
 									<td class="border-component-color">{{username}}</td>
 									<td class="border-component-color">{{emailAddress}}</td>
+									<td class="border-component-color">{{roles}}</td>
+									<td class="border-component-color">{{status}}</td>
 								</tr>
 								{{/each}}
 							</tbody>
 						</table>
+
 						{{#if hasMore}}
 							<button class="button secondary load-more {{isLoading}}">{{_ "Load_more"}}</button>
 						{{/if}}
@@ -58,3 +63,4 @@
 		{{/with}}
 	</div>
 </template>
+
diff --git a/packages/rocketchat-ui-flextab/client/tabs/membersList.js b/packages/rocketchat-ui-flextab/client/tabs/membersList.js
index c9e35fcd0a9268277c062c10a648c9d71a6731de..f6a92be62667b6b182830e6d04e8fdcb69a9b394 100644
--- a/packages/rocketchat-ui-flextab/client/tabs/membersList.js
+++ b/packages/rocketchat-ui-flextab/client/tabs/membersList.js
@@ -53,7 +53,11 @@ Template.membersList.helpers({
 			};
 		});
 
-		users = _.sortBy(users, u => u.user.username);
+		if (RocketChat.settings.get('UI_Use_Real_Name')) {
+			users = _.sortBy(users, u => u.user.name);
+		} else {
+			users = _.sortBy(users, u => u.user.username);
+		}
 		// show online users first.
 		// sortBy is stable, so we can do this
 		users = _.sortBy(users, u => u.status == null);
diff --git a/packages/rocketchat-ui-flextab/client/tabs/userInfo.html b/packages/rocketchat-ui-flextab/client/tabs/userInfo.html
index 7b5d52788e1fb21bc1a74bd877ec80e9a4afb5cd..4b1dfaffa3c5c667ad58b27cb6a5a19d9a365817 100644
--- a/packages/rocketchat-ui-flextab/client/tabs/userInfo.html
+++ b/packages/rocketchat-ui-flextab/client/tabs/userInfo.html
@@ -41,67 +41,69 @@
 			</div>
 			{{/with}}
 			<nav>
-				{{#if user.active}}
-					{{> videoButtons}}
-				{{/if}}
-				{{#if isDirect}}
-					{{#if isBlocker}}
-						<button class='button button-block tertiary unblock-user'><span><i class='icon-block'></i> {{_ "Unblock_User"}}</span></button>
-					{{else}}
-						<button class='button button-block danger block-user'><span><i class='icon-block'></i> {{_ "Block_User"}}</span></button>
+				{{#unless isSelf user.username}}
+					{{#if user.active}}
+						{{> videoButtons}}
 					{{/if}}
-				{{/if}}
-
-				{{#if showAll}}
-					{{#if canDirectMessage user.username}}
-						<button class='button button-block primary pvt-msg'><span><i class='icon-chat'></i> {{_ "Conversation"}}</span></button> {{/if}}
-					{{#if canSetOwner}}
-						{{#if isOwner}}
-							<button class="button button-block danger unset-owner"><span>{{_ "Remove_as_owner"}}</span></button>
+					{{#if isDirect}}
+						{{#if isBlocker}}
+							<button class='button button-block tertiary unblock-user'><span><i class='icon-block'></i> {{_ "Unblock_User"}}</span></button>
 						{{else}}
-							<button class="button button-block tertiary set-owner"><span>{{_ "Set_as_owner"}}</span></button>
+							<button class='button button-block danger block-user'><span><i class='icon-block'></i> {{_ "Block_User"}}</span></button>
 						{{/if}}
 					{{/if}}
-					{{#if canSetModerator}}
-						{{#if isModerator}}
-							<button class="button button-block danger unset-moderator"><span>{{_ "Remove_as_moderator"}}</span></button>
-						{{else}}
-							<button class="button button-block tertiary set-moderator"><span>{{_ "Set_as_moderator"}}</span></button>
+
+					{{#if showAll}}
+						{{#if canDirectMessage user.username}}
+							<button class='button button-block primary pvt-msg'><span><i class='icon-chat'></i> {{_ "Conversation"}}</span></button> {{/if}}
+						{{#if canSetOwner}}
+							{{#if isOwner}}
+								<button class="button button-block danger unset-owner"><span>{{_ "Remove_as_owner"}}</span></button>
+							{{else}}
+								<button class="button button-block tertiary set-owner"><span>{{_ "Set_as_owner"}}</span></button>
+							{{/if}}
 						{{/if}}
-					{{/if}}
-					{{#if canMuteUser}}
-						{{#if userMuted}}
-							<button class="button button-block secondary unmute-user primary"><span>{{_ "Unmute_user"}}</span></button>
-						{{else}}
-							<button class="button button-block danger mute-user"><span>{{_ "Mute_user"}}</span></button>
+						{{#if canSetModerator}}
+							{{#if isModerator}}
+								<button class="button button-block danger unset-moderator"><span>{{_ "Remove_as_moderator"}}</span></button>
+							{{else}}
+								<button class="button button-block tertiary set-moderator"><span>{{_ "Set_as_moderator"}}</span></button>
+							{{/if}}
+						{{/if}}
+						{{#if canMuteUser}}
+							{{#if userMuted}}
+								<button class="button button-block secondary unmute-user primary"><span>{{_ "Unmute_user"}}</span></button>
+							{{else}}
+								<button class="button button-block danger mute-user"><span>{{_ "Mute_user"}}</span></button>
+							{{/if}}
+						{{/if}}
+						{{#if canRemoveUser}}
+							<button class="button button-block danger remove-user"><span>{{_ "Remove_from_room"}}</span></button>
 						{{/if}}
 					{{/if}}
-					{{#if canRemoveUser}}
-						<button class="button button-block danger remove-user"><span>{{_ "Remove_from_room"}}</span></button>
-					{{/if}}
-				{{/if}}
 
-				{{#unless hideAdminControls}}
-					{{#if hasPermission 'edit-other-user-info'}}
-					<button class='button button-block primary edit-user'><span><i class='icon-edit'></i> {{_ "Edit"}}</span></button>
-					{{/if}}
-					{{#if hasPermission 'assign-admin-role'}}
-						{{#if hasAdminRole}}
-						<button class='button button-block danger remove-admin'><span><i class='icon-shield'></i> {{_ "Remove_Admin"}}</span></button>
-						{{else}}
-						<button class='button button-block secondary make-admin'><span><i class='icon-shield'></i> {{_ "Make_Admin"}}</span></button>
+					{{#unless hideAdminControls}}
+						{{#if hasPermission 'edit-other-user-info'}}
+						<button class='button button-block primary edit-user'><span><i class='icon-edit'></i> {{_ "Edit"}}</span></button>
 						{{/if}}
-					{{/if}}
-					{{#if hasPermission 'edit-other-user-active-status'}}
-						{{#if active}}
-						<button class='button button-block danger deactivate'><span><i class='icon-block'></i> {{_ "Deactivate"}}</span></button>
-						{{else}}
-						<button class='button button-block secondary activate'><span><i class='icon-ok-circled'></i> {{_ "Activate"}}</span></button>
+						{{#if hasPermission 'assign-admin-role'}}
+							{{#if hasAdminRole}}
+							<button class='button button-block danger remove-admin'><span><i class='icon-shield'></i> {{_ "Remove_Admin"}}</span></button>
+							{{else}}
+							<button class='button button-block secondary make-admin'><span><i class='icon-shield'></i> {{_ "Make_Admin"}}</span></button>
+							{{/if}}
 						{{/if}}
-					{{/if}}
-					{{#if hasPermission 'delete-user'}}
-					<button class='button button-block danger delete'><span><i class='icon-trash'></i> {{_ "Delete"}}</span></button>
-					{{/if}}
+						{{#if hasPermission 'edit-other-user-active-status'}}
+							{{#if active}}
+							<button class='button button-block danger deactivate'><span><i class='icon-block'></i> {{_ "Deactivate"}}</span></button>
+							{{else}}
+							<button class='button button-block secondary activate'><span><i class='icon-ok-circled'></i> {{_ "Activate"}}</span></button>
+							{{/if}}
+						{{/if}}
+						{{#if hasPermission 'delete-user'}}
+						<button class='button button-block danger delete'><span><i class='icon-trash'></i> {{_ "Delete"}}</span></button>
+						{{/if}}
+					{{/unless}}
 				{{/unless}}
 
 				{{#if showAll}}
diff --git a/packages/rocketchat-ui-flextab/client/tabs/userInfo.js b/packages/rocketchat-ui-flextab/client/tabs/userInfo.js
index 2ec2dbc6cd05cb344c1be576b4afb34f4bb85da3..e64f60b0e5fce222d385a1dff8d200d79d46a0aa 100644
--- a/packages/rocketchat-ui-flextab/client/tabs/userInfo.js
+++ b/packages/rocketchat-ui-flextab/client/tabs/userInfo.js
@@ -47,6 +47,11 @@ Template.userInfo.helpers({
 		return RocketChat.authz.hasAllPermission('create-d') && user && user.username !== username;
 	},
 
+	isSelf(username) {
+		const user = Meteor.user();
+		return user && user.username === username;
+	},
+
 	linkedinUsername() {
 		const user = Template.instance().user.get();
 		if (user && user.services && user.services.linkedin && user.services.linkedin.publicProfileUrl) {
diff --git a/packages/rocketchat-ui-master/client/main.js b/packages/rocketchat-ui-master/client/main.js
index ba187b5e968688c9a3c04bfb68820d774b7b5ba7..9e91eea36872af81eab8b181c983ef637197b076 100644
--- a/packages/rocketchat-ui-master/client/main.js
+++ b/packages/rocketchat-ui-master/client/main.js
@@ -110,7 +110,7 @@ Template.main.helpers({
 		return RocketChat.settings.get('Site_Name');
 	},
 	logged() {
-		if (Meteor.userId() != null || (RocketChat.settings.get('Accounts_AllowAnonymousAccess') === true && Session.get('forceLogin') !== true)) {
+		if (Meteor.userId() != null || (RocketChat.settings.get('Accounts_AllowAnonymousRead') === true && Session.get('forceLogin') !== true)) {
 			$('html').addClass('noscroll').removeClass('scroll');
 			return true;
 		} else {
@@ -134,7 +134,7 @@ Template.main.helpers({
 		return ready;
 	},
 	hasUsername() {
-		return (Meteor.userId() != null && Meteor.user().username != null) || (Meteor.userId() == null && RocketChat.settings.get('Accounts_AllowAnonymousAccess') === true);
+		return (Meteor.userId() != null && Meteor.user().username != null) || (Meteor.userId() == null && RocketChat.settings.get('Accounts_AllowAnonymousRead') === true);
 	},
 	requirePasswordChange() {
 		const user = Meteor.user();
diff --git a/packages/rocketchat-ui-message/client/messageBox.coffee b/packages/rocketchat-ui-message/client/messageBox.coffee
index 0052f3046146981a2e81895ecaec7b6f854b9b29..2587fda0c301ecbd128e41861c4d0fd4e683bed1 100644
--- a/packages/rocketchat-ui-message/client/messageBox.coffee
+++ b/packages/rocketchat-ui-message/client/messageBox.coffee
@@ -124,8 +124,11 @@ Template.messageBox.helpers
 	showSandstorm: ->
 		return Meteor.settings.public.sandstorm && !Meteor.isCordova
 
-	isAnonymous: ->
-		return not Meteor.userId()? and RocketChat.settings.get('Accounts_AllowAnonymousAccess') is true
+	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/)
@@ -186,6 +189,15 @@ Template.messageBox.events
 		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')
diff --git a/packages/rocketchat-ui-message/client/messageBox.html b/packages/rocketchat-ui-message/client/messageBox.html
index 02e98fe5a1072bbbcf279385ab5004ff0a8dd038..d8f6c14df52208a1db06d2f7f28347a61793892d 100644
--- a/packages/rocketchat-ui-message/client/messageBox.html
+++ b/packages/rocketchat-ui-message/client/messageBox.html
@@ -143,9 +143,12 @@
 				<button class="button join"><span><i class="icon-login"></i> {{_ "join"}}</span></button>
 			</div>
 			{{/if}}
-			{{#if isAnonymous}}
+			{{#if anonymousRead}}
 				<div>
-					<button class="button register"><span><i class="icon-login"></i> {{_ "Register_or_login_to_send_messages"}}</span></button>
+					<button class="button register"><span>{{_ "Sign_in_to_start_talking"}}</span></button>
+					{{#if anonymousWrite}}
+						<button class="button register-anonymous"><span>{{_ "Or_talk_as_anonymous"}}</span></button>
+					{{/if}}
 				</div>
 			{{/if}}
 		{{/with}}
diff --git a/packages/rocketchat-ui-sidenav/client/accountBox.js b/packages/rocketchat-ui-sidenav/client/accountBox.js
index d8dacd1406b2ab4408aa904f5410f1aa0f6a5e49..9825f9e1d68fe0f9c7f87af895993e723619d3dc 100644
--- a/packages/rocketchat-ui-sidenav/client/accountBox.js
+++ b/packages/rocketchat-ui-sidenav/client/accountBox.js
@@ -1,8 +1,9 @@
 Template.accountBox.helpers({
 	myUserInfo() {
-		if (Meteor.user() == null && RocketChat.settings.get('Accounts_AllowAnonymousAccess')) {
+		if (Meteor.user() == null && RocketChat.settings.get('Accounts_AllowAnonymousRead')) {
 			return {
 				name: t('Anonymous'),
+				fname: t('Anonymous'),
 				status: 'online',
 				visualStatus: t('online'),
 				username: 'anonymous'
@@ -24,12 +25,12 @@ Template.accountBox.helpers({
 				break;
 		}
 		return {
-			name: Session.get(`user_${ username }_name`),
+			name: Session.get(`user_${ username }_name`) || username,
 			status: Session.get(`user_${ username }_status`),
 			visualStatus,
 			_id: Meteor.userId(),
 			username,
-			fname: name
+			fname: name || username
 		};
 	},
 
@@ -50,7 +51,7 @@ Template.accountBox.events({
 	},
 
 	'click .account-box'() {
-		if (Meteor.userId() == null && RocketChat.settings.get('Accounts_AllowAnonymousAccess')) {
+		if (Meteor.userId() == null && RocketChat.settings.get('Accounts_AllowAnonymousRead')) {
 			return;
 		}
 
diff --git a/packages/rocketchat-ui-sidenav/client/createCombinedFlex.html b/packages/rocketchat-ui-sidenav/client/createCombinedFlex.html
index acc8e3230984e4dfb34f29eef7bb3475fb993f7c..bce0f8330976a13384dc4316d9910a7e5d816c74 100644
--- a/packages/rocketchat-ui-sidenav/client/createCombinedFlex.html
+++ b/packages/rocketchat-ui-sidenav/client/createCombinedFlex.html
@@ -8,18 +8,18 @@
 		<div class="wrapper">
 			<h4>{{_ "Create_new" }}</h4>
 			<div class="input-line no-icon">
-				<span>{{_ "Name"}}</span>
+				<label for="channel-name">{{_ "Name"}}</label>
 				<input type="text" id="channel-name" class="required" dir="auto" placeholder="{{_ 'Enter_name_here'}}">
 			</div>
 			<div class="input-line toggle">
-				<span>{{_ "Private"}}</span>
+				<label for="channel-type">{{_ "Private"}}</label>
 				<div class="input checkbox toggle">
 					<input type="checkbox" id="channel-type" {{privateSwitchDisabled}} {{privateSwitchChecked}}>
 					<label class="color-tertiary-font-color" for="channel-type"></label>
 				</div>
 			</div>
 			<div class="input-line toggle">
-				<span>{{_ "Read_only_channel"}}</span>
+				<label for="channel-ro">{{_ "Read_only_channel"}}</label>
 				<div class="input checkbox toggle">
 					<input type="checkbox" id="channel-ro">
 					<label class="color-tertiary-font-color" for="channel-ro"></label>
diff --git a/packages/rocketchat-ui-sidenav/client/createCombinedFlex.js b/packages/rocketchat-ui-sidenav/client/createCombinedFlex.js
index 8f17225e968420d337256d9252b5384f2462cfa2..4d4c16a39ab6aa1f09141d12e04aa8043134df95 100644
--- a/packages/rocketchat-ui-sidenav/client/createCombinedFlex.js
+++ b/packages/rocketchat-ui-sidenav/client/createCombinedFlex.js
@@ -111,7 +111,6 @@ Template.createCombinedFlex.events({
 		if (!err) {
 			return Meteor.call(createRoute, name, instance.selectedUsers.get(), readOnly, function(err, result) {
 				if (err) {
-					console.log(err);
 					if (err.error === 'error-invalid-name') {
 						instance.error.set({ invalid: true });
 						return;
@@ -137,7 +136,6 @@ Template.createCombinedFlex.events({
 				return FlowRouter.go(successRoute, { name }, FlowRouter.current().queryParams);
 			});
 		} else {
-			console.log(err);
 			return instance.error.set({ fields: err });
 		}
 	}
diff --git a/packages/rocketchat-ui-sidenav/client/directMessages.js b/packages/rocketchat-ui-sidenav/client/directMessages.js
index ae73370b526ffb36c996db5696b38c796370cbb3..68e09b80d82ec80f74255cb75e581a4b8f745ed0 100644
--- a/packages/rocketchat-ui-sidenav/client/directMessages.js
+++ b/packages/rocketchat-ui-sidenav/client/directMessages.js
@@ -7,6 +7,7 @@ Template.directMessages.helpers({
 
 	rooms() {
 		const query = { t: { $in: ['d']}, f: { $ne: true }, open: true };
+		const sort = { 't': 1 };
 
 		if (Meteor.user() && Meteor.user().settings && Meteor.user().settings.preferences && Meteor.user().settings.preferences.unreadRoomsMode) {
 			query.$or = [
@@ -15,6 +16,11 @@ Template.directMessages.helpers({
 			];
 		}
 
-		return ChatSubscription.find(query, { sort: { 't': 1, 'name': 1 }});
+		if (RocketChat.settings.get('UI_Use_Real_Name')) {
+			sort.fname = 1;
+		}
+		sort.name = 1;
+
+		return ChatSubscription.find(query, { sort });
 	}
 });
diff --git a/packages/rocketchat-ui-sidenav/client/toolbar.js b/packages/rocketchat-ui-sidenav/client/toolbar.js
index baa495d6a69592686b4be5aef3bab53282e997c4..c044c98dd280310d214e7feca0dd901559b3e1c2 100644
--- a/packages/rocketchat-ui-sidenav/client/toolbar.js
+++ b/packages/rocketchat-ui-sidenav/client/toolbar.js
@@ -158,7 +158,11 @@ Template.toolbar.helpers({
 					query.t = 'd';
 				}
 
-				query.name = new RegExp((RegExp.escape(filterText)), 'i');
+				const searchQuery = new RegExp((RegExp.escape(filterText)), 'i');
+				query.$or = [
+					{ name: searchQuery },
+					{ fname: searchQuery }
+				];
 
 				resultsFromClient = collection.find(query, {limit: 20, sort: {unread: -1, ls: -1}}).fetch();
 
diff --git a/packages/rocketchat-ui/client/lib/accounts.js b/packages/rocketchat-ui/client/lib/accounts.js
index 8d220aefcd8140538f81c5458fcf6c45ebb28ebd..5045fbff9dccef2d6b019959464fec6bd73cd9af 100644
--- a/packages/rocketchat-ui/client/lib/accounts.js
+++ b/packages/rocketchat-ui/client/lib/accounts.js
@@ -3,6 +3,7 @@ Accounts.onEmailVerificationLink(function(token, done) {
 	Accounts.verifyEmail(token, function(error) {
 		if (error == null) {
 			toastr.success(t('Email_verified'));
+			Meteor.call('afterVerifyEmail');
 		}
 		return done();
 	});
diff --git a/packages/rocketchat-ui/client/lib/chatMessages.coffee b/packages/rocketchat-ui/client/lib/chatMessages.coffee
index 42ddfe99488636ad8e0002168cb30240de0a92c8..bdeb2d72acda382f5cf597b44074848582831723 100644
--- a/packages/rocketchat-ui/client/lib/chatMessages.coffee
+++ b/packages/rocketchat-ui/client/lib/chatMessages.coffee
@@ -250,8 +250,9 @@ class @ChatMessages
 		$('.sweet-alert').addClass 'visible'
 
 	deleteMsg: (message) ->
+		forceDelete = RocketChat.authz.hasAtLeastOnePermission('force-delete-message', message.rid)
 		blockDeleteInMinutes = RocketChat.settings.get 'Message_AllowDeleting_BlockDeleteInMinutes'
-		if blockDeleteInMinutes? and blockDeleteInMinutes isnt 0
+		if blockDeleteInMinutes? and blockDeleteInMinutes isnt 0 and forceDelete is false
 			msgTs = moment(message.ts) if message.ts?
 			currentTsDiff = moment().diff(msgTs, 'minutes') if msgTs?
 			if currentTsDiff > blockDeleteInMinutes
diff --git a/packages/rocketchat-ui/client/lib/collections.js b/packages/rocketchat-ui/client/lib/collections.js
index 06b777c5ee6ce25be22d4c0d17ab8c807d07b393..3d95243fbce0653d373cbb94a9534095436fb176 100644
--- a/packages/rocketchat-ui/client/lib/collections.js
+++ b/packages/rocketchat-ui/client/lib/collections.js
@@ -17,7 +17,7 @@ RocketChat.models.Messages = _.extend({}, RocketChat.models.Messages, this.ChatM
 
 Meteor.startup(() => {
 	Tracker.autorun(() => {
-		if (!Meteor.userId() && RocketChat.settings.get('Accounts_AllowAnonymousAccess') === true) {
+		if (!Meteor.userId() && RocketChat.settings.get('Accounts_AllowAnonymousRead') === true) {
 			this.CachedChatRoom.init();
 			this.CachedChatSubscription.ready.set(true);
 		}
diff --git a/packages/rocketchat-ui/client/lib/fireEvent.js b/packages/rocketchat-ui/client/lib/fireEvent.js
index 4238ca12b395c2b54968b72c4b9859c823937d66..a3456d3e61a6d235a9b1d180b6c1be4c54ea2718 100644
--- a/packages/rocketchat-ui/client/lib/fireEvent.js
+++ b/packages/rocketchat-ui/client/lib/fireEvent.js
@@ -1,10 +1,18 @@
-window.fireGlobalEvent = (eventName, params) => {
+window.fireGlobalEvent = function _fireGlobalEvent(eventName, params) {
 	window.dispatchEvent(new CustomEvent(eventName, {detail: params}));
 
-	if (RocketChat.settings.get('Iframe_Integration_send_enable') === true) {
-		parent.postMessage({
-			eventName,
-			data: params
-		}, RocketChat.settings.get('Iframe_Integration_send_target_origin'));
-	}
+	Tracker.autorun((computation) => {
+		const enabled = RocketChat.settings.get('Iframe_Integration_send_enable');
+		if (enabled === undefined) {
+			return;
+		}
+		computation.stop();
+		if (enabled) {
+			parent.postMessage({
+				eventName,
+				data: params
+			}, RocketChat.settings.get('Iframe_Integration_send_target_origin'));
+		}
+	});
 };
+
diff --git a/packages/rocketchat-ui/client/views/app/room.coffee b/packages/rocketchat-ui/client/views/app/room.coffee
index 05f1646ef9ce023abf9a20278b2fd7e39625a9a0..edd6c1b8a5dc159718a37d7c0c5df6738a96ec65 100644
--- a/packages/rocketchat-ui/client/views/app/room.coffee
+++ b/packages/rocketchat-ui/client/views/app/room.coffee
@@ -181,7 +181,7 @@ Template.room.helpers
 		if room.t isnt 'c'
 			return true
 
-		if RocketChat.settings.get('Accounts_AllowAnonymousAccess') is true
+		if RocketChat.settings.get('Accounts_AllowAnonymousRead') is true
 			return true
 
 		if RocketChat.authz.hasAllPermission('preview-c-room')
diff --git a/packages/rocketchat-webrtc/WebRTCClass.coffee b/packages/rocketchat-webrtc/WebRTCClass.coffee
new file mode 100644
index 0000000000000000000000000000000000000000..a957e411d7cc480832d41bd9cdc3ce66a4715c50
--- /dev/null
+++ b/packages/rocketchat-webrtc/WebRTCClass.coffee
@@ -0,0 +1,823 @@
+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/server/methods/afterVerifyEmail.js b/server/methods/afterVerifyEmail.js
new file mode 100644
index 0000000000000000000000000000000000000000..7b92f596167bb016ec8df36ede3bee8d8f40cd06
--- /dev/null
+++ b/server/methods/afterVerifyEmail.js
@@ -0,0 +1,20 @@
+Meteor.methods({
+	afterVerifyEmail() {
+		const userId = Meteor.userId();
+
+		if (!userId) {
+			throw new Meteor.Error('error-invalid-user', 'Invalid user', {
+				method: 'afterVerifyEmail'
+			});
+		}
+
+		const user = RocketChat.models.Users.findOneById(userId);
+
+		const verifiedEmail = _.find(user.emails, (email) => email.verified);
+
+		if (verifiedEmail) {
+			RocketChat.models.Roles.addUserRoles(user._id, 'user');
+			RocketChat.models.Roles.removeUserRoles(user._id, 'anonymous');
+		}
+	}
+});
diff --git a/server/methods/canAccessRoom.js b/server/methods/canAccessRoom.js
index 8c4a04ad98a97e31fd6907f00879ec05de5b0641..dbb1d468b448a67a6dfac2a5c72b648996756b66 100644
--- a/server/methods/canAccessRoom.js
+++ b/server/methods/canAccessRoom.js
@@ -5,7 +5,7 @@ Meteor.methods({
 
 		let user;
 
-		if (!userId && RocketChat.settings.get('Accounts_AllowAnonymousAccess') === false) {
+		if (!userId && RocketChat.settings.get('Accounts_AllowAnonymousRead') === false) {
 			throw new Meteor.Error('error-invalid-user', 'Invalid user', {
 				method: 'canAccessRoom'
 			});
diff --git a/server/methods/getUsernameSuggestion.js b/server/methods/getUsernameSuggestion.js
index 30440f1513975c99ca3d758d6f4da27efb97bad3..265342330055420b71f316b1068329a233129faf 100644
--- a/server/methods/getUsernameSuggestion.js
+++ b/server/methods/getUsernameSuggestion.js
@@ -92,7 +92,7 @@ function generateSuggestion(user) {
 	}
 
 	if (usernames.length === 0 || usernames[0].length === 0) {
-		usernames.push('user');
+		usernames.push(RocketChat.settings.get('Accounts_DefaultUsernamePrefixSuggestion'));
 	}
 
 	let index = 0;
diff --git a/server/methods/loadHistory.js b/server/methods/loadHistory.js
index 82f9d01590bb2cd8e8c0c1c9787ea2d67f28851d..01d1720ec5c0d85b759ae4f32c31baecc91d35b6 100644
--- a/server/methods/loadHistory.js
+++ b/server/methods/loadHistory.js
@@ -21,7 +21,7 @@ Meteor.methods({
 	loadHistory(rid, end, limit = 20, ls) {
 		check(rid, String);
 
-		if (!Meteor.userId() && RocketChat.settings.get('Accounts_AllowAnonymousAccess') === false) {
+		if (!Meteor.userId() && RocketChat.settings.get('Accounts_AllowAnonymousRead') === false) {
 			throw new Meteor.Error('error-invalid-user', 'Invalid user', {
 				method: 'loadHistory'
 			});
@@ -34,7 +34,7 @@ Meteor.methods({
 			return false;
 		}
 
-		const canAnonymous = RocketChat.settings.get('Accounts_AllowAnonymousAccess');
+		const canAnonymous = RocketChat.settings.get('Accounts_AllowAnonymousRead');
 		const canPreview = RocketChat.authz.hasPermission(fromId, 'preview-c-room');
 		if (room.t === 'c' && !canAnonymous && !canPreview && room.usernames.indexOf(room.username) === -1) {
 			return false;
diff --git a/server/methods/registerUser.js b/server/methods/registerUser.js
index a4e45bf0fa871bcf5ff7f6b9cae90ec716367751..17c0ceb71b149548bce2379f44efd857692ab760 100644
--- a/server/methods/registerUser.js
+++ b/server/methods/registerUser.js
@@ -1,11 +1,25 @@
 Meteor.methods({
 	registerUser(formData) {
-		check(formData, Match.ObjectIncluding({
-			email: String,
-			pass: String,
-			name: String,
-			secretURL: Match.Optional(String)
-		}));
+		const AllowAnonymousRead = RocketChat.settings.get('Accounts_AllowAnonymousRead');
+		const AllowAnonymousWrite = RocketChat.settings.get('Accounts_AllowAnonymousWrite');
+		if (AllowAnonymousRead === true && AllowAnonymousWrite === true && formData.email == null) {
+			const userId = Accounts.insertUserDoc({}, {
+				globalRoles: [
+					'anonymous'
+				]
+			});
+
+			const { id, token } = Accounts._loginUser(this, userId);
+
+			return { id, token };
+		} else {
+			check(formData, Match.ObjectIncluding({
+				email: String,
+				pass: String,
+				name: String,
+				secretURL: Match.Optional(String)
+			}));
+		}
 
 		if (RocketChat.settings.get('Accounts_RegistrationForm') === 'Disabled') {
 			throw new Meteor.Error('error-user-registration-disabled', 'User registration is disabled', { method: 'registerUser' });
diff --git a/server/methods/saveUserProfile.js b/server/methods/saveUserProfile.js
index a70d7aab30f521a45c23302c785036ef1f7ba7a8..7f78f1c99e399c142121f52807efd991316c58aa 100644
--- a/server/methods/saveUserProfile.js
+++ b/server/methods/saveUserProfile.js
@@ -31,17 +31,6 @@ Meteor.methods({
 			}
 			return true;
 		}
-		if ((settings.newPassword) && RocketChat.settings.get('Accounts_AllowPasswordChange') === true) {
-			if (!checkPassword(user, settings.typedPassword)) {
-				throw new Meteor.Error('error-invalid-password', 'Invalid password', {
-					method: 'saveUserProfile'
-				});
-			}
-
-			Accounts.setPassword(Meteor.userId(), settings.newPassword, {
-				logout: false
-			});
-		}
 
 		if (settings.realname) {
 			RocketChat.setRealName(Meteor.userId(), settings.realname);
@@ -61,6 +50,19 @@ Meteor.methods({
 			Meteor.call('setEmail', settings.email);
 		}
 
+		// Should be the last chack to prevent error when trying to check password for users without password
+		if ((settings.newPassword) && RocketChat.settings.get('Accounts_AllowPasswordChange') === true) {
+			if (!checkPassword(user, settings.typedPassword)) {
+				throw new Meteor.Error('error-invalid-password', 'Invalid password', {
+					method: 'saveUserProfile'
+				});
+			}
+
+			Accounts.setPassword(Meteor.userId(), settings.newPassword, {
+				logout: false
+			});
+		}
+
 		RocketChat.models.Users.setProfile(Meteor.userId(), {});
 
 		RocketChat.saveCustomFields(Meteor.userId(), customFields);
diff --git a/server/publications/room.js b/server/publications/room.js
index 8ca4f3248a97d30264116aa1873220e1963fcd46..76a2f29ed1b841cb8978a884574b8363902ef1a0 100644
--- a/server/publications/room.js
+++ b/server/publications/room.js
@@ -40,7 +40,7 @@ const roomMap = (record) => {
 Meteor.methods({
 	'rooms/get'(updatedAt) {
 		if (!Meteor.userId()) {
-			if (RocketChat.settings.get('Accounts_AllowAnonymousAccess') === true) {
+			if (RocketChat.settings.get('Accounts_AllowAnonymousRead') === true) {
 				return RocketChat.models.Rooms.findByDefaultAndTypes(true, ['c'], options).fetch();
 			}
 			return [];
@@ -59,7 +59,7 @@ Meteor.methods({
 	},
 
 	getRoomByTypeAndName(type, name) {
-		if (!Meteor.userId() && RocketChat.settings.get('Accounts_AllowAnonymousAccess') === false) {
+		if (!Meteor.userId() && RocketChat.settings.get('Accounts_AllowAnonymousRead') === false) {
 			throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getRoomByTypeAndName' });
 		}
 
diff --git a/server/publications/spotlight.js b/server/publications/spotlight.js
index ceccb8f4c9e62b36e679cc3c5463ac24e6d6846f..babbe04290c01aa51d517110d8f6af10b63f755f 100644
--- a/server/publications/spotlight.js
+++ b/server/publications/spotlight.js
@@ -19,7 +19,7 @@ Meteor.methods({
 		const regex = new RegExp(s.trim(s.escapeRegExp(text)), 'i');
 
 		if (this.userId == null) {
-			if (RocketChat.settings.get('Accounts_AllowAnonymousAccess') === true) {
+			if (RocketChat.settings.get('Accounts_AllowAnonymousRead') === true) {
 				result.rooms = RocketChat.models.Rooms.findByNameAndTypeNotDefault(regex, 'c', roomOptions).fetch();
 			}
 			return result;
diff --git a/server/startup/migrations/v093.js b/server/startup/migrations/v093.js
new file mode 100644
index 0000000000000000000000000000000000000000..ee2b645e1130f299b33b71b237c37b3e1c54313c
--- /dev/null
+++ b/server/startup/migrations/v093.js
@@ -0,0 +1,32 @@
+RocketChat.Migrations.add({
+	version: 93,
+	up() {
+
+		if (RocketChat && RocketChat.models && RocketChat.models.Settings) {
+			const setting = RocketChat.models.Settings.findOne({ _id: 'Accounts_AllowAnonymousAccess' });
+			if (setting && setting.value === true) {
+				RocketChat.models.Settings.update({ _id: 'Accounts_AllowAnonymousRead' }, { $set: { value: setting.value } });
+			}
+		}
+
+		const query = {
+			_id: {
+				$in: [
+					'view-c-room',
+					'view-history',
+					'view-joined-room',
+					'view-p-room',
+					'preview-c-room'
+				]
+			}
+		};
+
+		const update = {
+			$addToSet: {
+				roles: 'anonymous'
+			}
+		};
+
+		RocketChat.models.Permissions.update(query, update, { multi: true });
+	}
+});
diff --git a/tests/end-to-end/api/01-users.js b/tests/end-to-end/api/01-users.js
index e5fa8fc3eb083a1168949bd1086d603ea945844c..6d8e42bc3755dff0e20a9876c81f5d8accba1b98 100644
--- a/tests/end-to-end/api/01-users.js
+++ b/tests/end-to-end/api/01-users.js
@@ -5,7 +5,6 @@
 import {getCredentials, api, login, request, credentials, apiEmail, apiUsername, targetUser, log} from '../../data/api-data.js';
 import {adminEmail, password} from '../../data/user.js';
 import {imgURL} from '../../data/interactions.js';
-import supertest from 'supertest';
 
 describe('Users', function() {
 	this.retries(0);
@@ -14,133 +13,257 @@ describe('Users', function() {
 
 	it('/users.create', (done) => {
 		request.post(api('users.create'))
-			.set(credentials)
-			.send({
-				email: apiEmail,
-				name: apiUsername,
-				username: apiUsername,
-				password,
-				active: true,
-				roles: ['user'],
-				joinDefaultChannels: true,
-				verified:true
-			})
-			.expect('Content-Type', 'application/json')
-			.expect(200)
-			.expect((res) => {
-				expect(res.body).to.have.property('success', true);
-				expect(res.body).to.have.deep.property('user.username', apiUsername);
-				expect(res.body).to.have.deep.property('user.emails[0].address', apiEmail);
-				expect(res.body).to.have.deep.property('user.active', true);
-				expect(res.body).to.have.deep.property('user.name', apiUsername);
-				targetUser._id = res.body.user._id;
-			})
-			.end(done);
+		.set(credentials)
+		.send({
+			email: apiEmail,
+			name: apiUsername,
+			username: apiUsername,
+			password,
+			active: true,
+			roles: ['user'],
+			joinDefaultChannels: true,
+			verified:true
+		})
+		.expect('Content-Type', 'application/json')
+		.expect(200)
+		.expect((res) => {
+			expect(res.body).to.have.property('success', true);
+			expect(res.body).to.have.deep.property('user.username', apiUsername);
+			expect(res.body).to.have.deep.property('user.emails[0].address', apiEmail);
+			expect(res.body).to.have.deep.property('user.active', true);
+			expect(res.body).to.have.deep.property('user.name', apiUsername);
+			targetUser._id = res.body.user._id;
+		})
+		.end(done);
 	});
 
 	it('/users.info', (done) => {
 		request.get(api('users.info'))
-			.set(credentials)
-			.query({
-				userId: targetUser._id
-			})
-			.expect('Content-Type', 'application/json')
-			.expect(200)
-			.expect((res) => {
-				expect(res.body).to.have.property('success', true);
-				expect(res.body).to.have.deep.property('user.username', apiUsername);
-				expect(res.body).to.have.deep.property('user.emails[0].address', apiEmail);
-				expect(res.body).to.have.deep.property('user.active', true);
-				expect(res.body).to.have.deep.property('user.name', apiUsername);
-			})
-			.end(done);
+		.set(credentials)
+		.query({
+			userId: targetUser._id
+		})
+		.expect('Content-Type', 'application/json')
+		.expect(200)
+		.expect((res) => {
+			expect(res.body).to.have.property('success', true);
+			expect(res.body).to.have.deep.property('user.username', apiUsername);
+			expect(res.body).to.have.deep.property('user.emails[0].address', apiEmail);
+			expect(res.body).to.have.deep.property('user.active', true);
+			expect(res.body).to.have.deep.property('user.name', apiUsername);
+		})
+		.end(done);
 	});
 
 	it('/users.getPresence', (done) => {
 		request.get(api('users.getPresence'))
-			.set(credentials)
-			.query({
-				userId: targetUser._id
-			})
-			.expect('Content-Type', 'application/json')
-			.expect(200)
-			.expect((res) => {
-				expect(res.body).to.have.property('success', true);
-				expect(res.body).to.have.deep.property('presence', 'offline');
-			})
-			.end(done);
+		.set(credentials)
+		.query({
+			userId: targetUser._id
+		})
+		.expect('Content-Type', 'application/json')
+		.expect(200)
+		.expect((res) => {
+			expect(res.body).to.have.property('success', true);
+			expect(res.body).to.have.deep.property('presence', 'offline');
+		})
+		.end(done);
 	});
 
 	it('/users.list', (done) => {
 		request.get(api('users.list'))
-			.set(credentials)
-			.expect('Content-Type', 'application/json')
-			.expect(200)
-			.expect((res) => {
-				expect(res.body).to.have.property('success', true);
-				expect(res.body).to.have.property('count');
-				expect(res.body).to.have.property('total');
-			})
-			.end(done);
+		.set(credentials)
+		.expect('Content-Type', 'application/json')
+		.expect(200)
+		.expect((res) => {
+			expect(res.body).to.have.property('success', true);
+			expect(res.body).to.have.property('count');
+			expect(res.body).to.have.property('total');
+		})
+		.end(done);
 	});
 
 	it.skip('/users.list', (done) => {
-	//filtering user list
+		//filtering user list
 		request.get(api('users.list'))
-			.set(credentials)
-			.query({
-				name: { '$regex': 'g' }
-			})
-			.field('username', 1)
-			.sort('createdAt', -1)
-			.expect(log)
-			.expect('Content-Type', 'application/json')
-			.expect(200)
-			.expect((res) => {
-				expect(res.body).to.have.property('success', true);
-				expect(res.body).to.have.property('count');
-				expect(res.body).to.have.property('total');
-			})
-			.end(done);
+		.set(credentials)
+		.query({
+			name: { '$regex': 'g' }
+		})
+		.field('username', 1)
+		.sort('createdAt', -1)
+		.expect(log)
+		.expect('Content-Type', 'application/json')
+		.expect(200)
+		.expect((res) => {
+			expect(res.body).to.have.property('success', true);
+			expect(res.body).to.have.property('count');
+			expect(res.body).to.have.property('total');
+		})
+		.end(done);
 	});
 
 
 
 	it.skip('/users.setAvatar', (done) => {
 		request.post(api('users.setAvatar'))
-			.set(credentials)
-			.attach('avatarUrl', imgURL)
-			.expect('Content-Type', 'application/json')
-			.expect(200)
-			.expect((res) => {
-				expect(res.body).to.have.property('success', true);
-			})
-			.end(done);
+		.set(credentials)
+		.attach('avatarUrl', imgURL)
+		.expect('Content-Type', 'application/json')
+		.expect(200)
+		.expect((res) => {
+			expect(res.body).to.have.property('success', true);
+		})
+		.end(done);
 	});
 
 	it('/users.update', (done) => {
 		request.post(api('users.update'))
+		.set(credentials)
+		.send({
+			userId: targetUser._id,
+			data :{
+				email: apiEmail,
+				name: `edited${ apiUsername }`,
+				username: `edited${ apiUsername }`,
+				password,
+				active: true,
+				roles: ['user']
+			}
+		})
+		.expect('Content-Type', 'application/json')
+		.expect(200)
+		.expect((res) => {
+			expect(res.body).to.have.property('success', true);
+			expect(res.body).to.have.deep.property('user.username', `edited${ apiUsername }`);
+			expect(res.body).to.have.deep.property('user.emails[0].address', apiEmail);
+			expect(res.body).to.have.deep.property('user.active', true);
+			expect(res.body).to.have.deep.property('user.name', `edited${ apiUsername }`);
+		})
+		.end(done);
+	});
+
+	describe('/users.createToken', () => {
+		let user;
+		beforeEach((done) => {
+			const username = `user.test.${ Date.now() }`;
+			const email = `${ username }@rocket.chat`;
+			request.post(api('users.create'))
 			.set(credentials)
+			.send({ email, name: username, username, password })
+			.end((err, res) => {
+				user = res.body.user;
+				done();
+			});
+		});
+
+		let userCredentials;
+		beforeEach((done) => {
+			request.post(api('login'))
 			.send({
-				userId: targetUser._id,
-				data :{
-					email: apiEmail,
-					name: `edited${ apiUsername }`,
-					username: `edited${ apiUsername }`,
-					password,
-					active: true,
-					roles: ['user']
-				}
+				user: user.username,
+				password
 			})
 			.expect('Content-Type', 'application/json')
 			.expect(200)
 			.expect((res) => {
-				expect(res.body).to.have.property('success', true);
-				expect(res.body).to.have.deep.property('user.username', `edited${ apiUsername }`);
-				expect(res.body).to.have.deep.property('user.emails[0].address', apiEmail);
-				expect(res.body).to.have.deep.property('user.active', true);
-				expect(res.body).to.have.deep.property('user.name', `edited${ apiUsername }`);
+				userCredentials = {};
+				userCredentials['X-Auth-Token'] = res.body.data.authToken;
+				userCredentials['X-User-Id'] = res.body.data.userId;
 			})
 			.end(done);
+		});
+		afterEach(done => {
+			request.post(api('users.delete')).set(credentials).send({
+				userId: user._id
+			}).end(done);
+			user = undefined;
+		});
+
+		describe('logged as admin', () => {
+			it('should return the user id and a new token', (done) => {
+				request.post(api('users.createToken'))
+				.set(credentials)
+				.send({
+					username: user.username
+				})
+				.expect('Content-Type', 'application/json')
+				.expect(200)
+				.expect((res) => {
+					expect(res.body).to.have.property('success', true);
+					expect(res.body).to.have.deep.property('data.userId', user._id);
+					expect(res.body).to.have.deep.property('data.authToken');
+				})
+				.end(done);
+			});
+		});
+
+		describe('logged as itself', () => {
+			it('should return the user id and a new token', (done) => {
+				request.post(api('users.createToken'))
+				.set(userCredentials)
+				.send({
+					username: user.username
+				})
+				.expect('Content-Type', 'application/json')
+				.expect(200)
+				.expect((res) => {
+					expect(res.body).to.have.property('success', true);
+					expect(res.body).to.have.deep.property('data.userId', user._id);
+					expect(res.body).to.have.deep.property('data.authToken');
+				})
+				.end(done);
+			});
+		});
+
+		describe('As an user not allowed', () => {
+			it('should return 401 unauthorized', (done) => {
+				request.post(api('users.createToken'))
+				.set(userCredentials)
+				.send({
+					username: 'rocket.cat'
+				})
+				.expect('Content-Type', 'application/json')
+				.expect(400)
+				.expect((res) => {
+					expect(res.body).to.have.property('errorType');
+					expect(res.body).to.have.property('error');
+				})
+				.end(done);
+			});
+		});
+
+		describe('Not logged in', () => {
+			it('should return 401 unauthorized', (done) => {
+				request.post(api('users.createToken'))
+				.send({
+					username: user.username
+				})
+				.expect('Content-Type', 'application/json')
+				.expect(401)
+				.expect((res) => {
+					expect(res.body).to.have.property('message');
+				})
+				.end(done);
+			});
+		});
+
+		describe('Testing if the returned token is valid', (done) => {
+			it('should return 200', (done) => {
+				return request.post(api('users.createToken'))
+				.set(credentials)
+				.send({ username: user.username })
+				.expect('Content-Type', 'application/json')
+				.end((err, res) => {
+					return err ? done () : request.get(api('me'))
+					.set({ 'X-Auth-Token': `${ res.body.data.authToken }`, 'X-User-Id': res.body.data.userId })
+					.expect(200)
+					.expect((res) => {
+						expect(res.body).to.have.property('success', true);
+					})
+					.end(done);
+				});
+			});
+		});
 	});
 });
diff --git a/tests/end-to-end/ui/07-emoji.js b/tests/end-to-end/ui/07-emoji.js
index 39e27f94669f879bfd90d37d957a879da731762d..2038bc8c46298c7aba2ec5cccc6036c9f720d15c 100644
--- a/tests/end-to-end/ui/07-emoji.js
+++ b/tests/end-to-end/ui/07-emoji.js
@@ -127,7 +127,7 @@ describe('emoji', ()=> {
 		});
 
 		it('the value on the message input should be the same as the emoji clicked', ()=> {
-			mainContent.messageInput.getValue().should.equal(':smile:');
+			mainContent.messageInput.getValue().should.equal(':smile: ');
 		});
 
 		it('send the emoji', ()=> {