diff --git a/.meteor/packages b/.meteor/packages
index b1fccebabf03..91db90974b71 100644
--- a/.meteor/packages
+++ b/.meteor/packages
@@ -154,6 +154,7 @@ rocketchat:ui-vrecord
rocketchat:user-data-download
rocketchat:version
rocketchat:videobridge
+rocketchat:webdav
rocketchat:webrtc
rocketchat:wordpress
rocketchat:nrr
diff --git a/.meteor/versions b/.meteor/versions
index 9a11dd0ee272..ebbb45a870c4 100644
--- a/.meteor/versions
+++ b/.meteor/versions
@@ -250,6 +250,7 @@ rocketchat:user-data-download@1.0.0
rocketchat:version@1.0.0
rocketchat:version-check@0.0.1
rocketchat:videobridge@0.2.0
+rocketchat:webdav@0.0.1
rocketchat:webrtc@0.0.1
rocketchat:wordpress@0.0.1
routepolicy@1.0.13
diff --git a/packages/rocketchat-file-upload/ufs/Webdav/server.js b/packages/rocketchat-file-upload/ufs/Webdav/server.js
index d437317e1931..19c46700815a 100644
--- a/packages/rocketchat-file-upload/ufs/Webdav/server.js
+++ b/packages/rocketchat-file-upload/ufs/Webdav/server.js
@@ -12,7 +12,6 @@ export class WebdavStore extends UploadFS.Store {
super(options);
-
const client = new Webdav(
options.connection.credentials.server,
options.connection.credentials.username,
@@ -127,7 +126,6 @@ export class WebdavStore extends UploadFS.Store {
writeStream.pipe(webdavStream);
return writeStream;
};
-
}
}
diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json
index 2aecb1725cae..bf7f12cc6d85 100644
--- a/packages/rocketchat-i18n/i18n/en.i18n.json
+++ b/packages/rocketchat-i18n/i18n/en.i18n.json
@@ -596,6 +596,7 @@
"Copy": "Copy",
"Copy_to_clipboard": "Copy to clipboard",
"COPY_TO_CLIPBOARD": "COPY TO CLIPBOARD",
+ "could-not-access-webdav": "Could not access WebDAV",
"Count": "Count",
"Country": "Country",
"Country_Afghanistan": "Afghanistan",
@@ -976,6 +977,7 @@
"Drop_to_upload_file": "Drop to upload file",
"Dry_run": "Dry run",
"Dry_run_description": "Will only send one email, to the same address as in From. The email must belong to a valid user.",
+ "duplicated-account": "Duplicated account",
"Duplicate_archived_channel_name": "An archived Channel with name `#%s` exists",
"Duplicate_archived_private_group_name": "An archived Private Group with name '%s' exists",
"Duplicate_channel_name": "A Channel with name '%s' exists",
@@ -1078,6 +1080,7 @@
"error-importer-not-defined": "The importer was not defined correctly, it is missing the Import class.",
"error-input-is-not-a-valid-field": "__input__ is not a valid __field__",
"error-invalid-actionlink": "Invalid action link",
+ "error-invalid-account": "Invalid Account",
"error-invalid-arguments": "Invalid arguments",
"error-invalid-asset": "Invalid asset",
"error-invalid-channel": "Invalid channel.",
@@ -2295,6 +2298,7 @@
"Save_changes": "Save changes",
"Save_Mobile_Bandwidth": "Save Mobile Bandwidth",
"Save_to_enable_this_action": "Save to enable this action",
+ "Save_To_Webdav": "Save to WebDAV",
"Save_your_encryption_password": "Save your encryption password",
"Saved": "Saved",
"Saving": "Saving",
@@ -2675,7 +2679,9 @@
"Upload_file_description": "File description",
"Upload_file_name": "File name",
"Upload_file_question": "Upload file?",
+ "Upload_Folder_Path": "Upload Folder Path",
"Upload_user_avatar": "Upload avatar",
+ "Upload_From" : "Upload from __name__",
"Uploading_file": "Uploading file...",
"Uptime": "Uptime",
"URL": "URL",
@@ -2843,6 +2849,13 @@
"We_are_offline_Sorry_for_the_inconvenience": "We are offline. Sorry for the inconvenience.",
"We_have_sent_password_email": "We have sent you an email with password reset instructions. If you do not receive an email shortly, please come back and try again.",
"We_have_sent_registration_email": "We have sent you an email to confirm your registration. If you do not receive an email shortly, please come back and try again.",
+ "WebDAV_Accounts": "WebDAV Accounts",
+ "Webdav_add_new_account": "Add new WebDAV account",
+ "webdav-account-saved": "WebDAV account saved",
+ "Webdav_Integration_Enabled": "Webdav Integration Enabled",
+ "Webdav_Server_URL": "WebDAV Server Access URL",
+ "Webdav_Username": "WebDAV Username",
+ "Webdav_Password": "WebDAV Password",
"Webhook_URL": "Webhook URL",
"Webhooks": "Webhooks",
"WebRTC_direct_audio_call_from_%s": "Direct audio call from %s",
diff --git a/packages/rocketchat-lib/lib/messageBox.js b/packages/rocketchat-lib/lib/messageBox.js
index 321ea63c7154..514fb1258ffb 100644
--- a/packages/rocketchat-lib/lib/messageBox.js
+++ b/packages/rocketchat-lib/lib/messageBox.js
@@ -1,4 +1,6 @@
-RocketChat.messageBox = {};
+import EventEmitter from 'wolfy87-eventemitter';
+
+RocketChat.messageBox = new EventEmitter;
RocketChat.messageBox.actions = new class {
constructor() {
@@ -31,7 +33,12 @@ RocketChat.messageBox.actions = new class {
this.actions[group].push({ ...config, label });
}
-
+ remove(group, expression) {
+ if (!group || !this.actions[group]) {
+ return false;
+ }
+ return (this.actions[group] = this.actions[group].filter((action) => expression.test(action.id)));
+ }
get(group) {
if (!group) {
return Object.keys(this.actions).reduce((ret, key) => {
diff --git a/packages/rocketchat-ui-account/client/accountFlex.html b/packages/rocketchat-ui-account/client/accountFlex.html
index 3033f12a8804..c8ba335c8b8c 100644
--- a/packages/rocketchat-ui-account/client/accountFlex.html
+++ b/packages/rocketchat-ui-account/client/accountFlex.html
@@ -22,6 +22,8 @@
{{> sidebarItem menuItem "Encryption" "key" "account" "encryption" }}
{{/if}}
+ {{> sidebarItem menuItem "Integrations" "code" "account" "integrations" }}
+
{{#if accessTokensEnabled}}
{{> sidebarItem menuItem "Personal_Access_Tokens" "key" "account" "tokens" }}
{{/if}}
diff --git a/packages/rocketchat-ui-account/client/accountIntegrations.html b/packages/rocketchat-ui-account/client/accountIntegrations.html
new file mode 100644
index 000000000000..9448566c99db
--- /dev/null
+++ b/packages/rocketchat-ui-account/client/accountIntegrations.html
@@ -0,0 +1,28 @@
+
+
+ {{> header sectionName="Integrations"}}
+
+
+
+
diff --git a/packages/rocketchat-ui-account/client/accountIntegrations.js b/packages/rocketchat-ui-account/client/accountIntegrations.js
new file mode 100644
index 000000000000..1a4185294e36
--- /dev/null
+++ b/packages/rocketchat-ui-account/client/accountIntegrations.js
@@ -0,0 +1,33 @@
+/* global */
+
+import toastr from 'toastr';
+
+Template.accountIntegrations.helpers({
+ webdavAccounts() {
+ return RocketChat.models.WebdavAccounts.find().fetch();
+ },
+ getOptionValue(account) {
+ return account.name || `${ account.username }@${ account.server_url.replace(/^https?\:\/\//i, '') }`;
+ },
+});
+
+Template.accountIntegrations.events({
+ 'click .webdav-account-remove'(e) {
+ e.preventDefault();
+ const selectEl = document.getElementById('webdav-accounts');
+ const { options } = selectEl;
+ const selectedOption = selectEl.value;
+ const optionIndex = Array.from(options).findIndex((option) => option.value === selectedOption);
+
+ Meteor.call('removeWebdavAccount', selectedOption, function(error) {
+ if (error) {
+ return toastr.error(t(error.error));
+ }
+
+ toastr.success(t('webdav-account-removed'));
+ modal.close();
+ });
+
+ selectEl.remove(optionIndex);
+ },
+});
diff --git a/packages/rocketchat-ui-account/package.js b/packages/rocketchat-ui-account/package.js
index e7a65cdcee53..1644d5a587a6 100644
--- a/packages/rocketchat-ui-account/package.js
+++ b/packages/rocketchat-ui-account/package.js
@@ -21,6 +21,7 @@ Package.onUse(function(api) {
api.addFiles('client/account.html', 'client');
api.addFiles('client/accountFlex.html', 'client');
+ api.addFiles('client/accountIntegrations.html', 'client');
api.addFiles('client/accountPreferences.html', 'client');
api.addFiles('client/accountProfile.html', 'client');
api.addFiles('client/avatar/avatar.html', 'client');
@@ -28,6 +29,7 @@ Package.onUse(function(api) {
api.addFiles('client/account.js', 'client');
api.addFiles('client/accountFlex.js', 'client');
+ api.addFiles('client/accountIntegrations.js', 'client');
api.addFiles('client/accountPreferences.js', 'client');
api.addFiles('client/accountProfile.js', 'client');
api.addFiles('client/avatar/avatar.js', 'client');
diff --git a/packages/rocketchat-ui-master/public/icons.svg b/packages/rocketchat-ui-master/public/icons.svg
index bd06c6df92c6..dcd151205cc0 100644
--- a/packages/rocketchat-ui-master/public/icons.svg
+++ b/packages/rocketchat-ui-master/public/icons.svg
@@ -460,6 +460,22 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/rocketchat-ui-message/client/messageBox.js b/packages/rocketchat-ui-message/client/messageBox.js
index 02a5121e1949..92ea0e42864f 100644
--- a/packages/rocketchat-ui-message/client/messageBox.js
+++ b/packages/rocketchat-ui-message/client/messageBox.js
@@ -653,6 +653,7 @@ Template.messageBox.onCreated(function() {
this.dataReply = new ReactiveVar(''); // if user is replying to a mssg, this will contain data of the mssg being replied to
this.isMessageFieldEmpty = new ReactiveVar(true);
this.sendIcon = new ReactiveVar(false);
+ RocketChat.messageBox.emit('created', this);
});
Meteor.startup(function() {
diff --git a/packages/rocketchat-ui/client/views/app/modal.js b/packages/rocketchat-ui/client/views/app/modal.js
index 61b047a38ffb..5991ba4e26f7 100644
--- a/packages/rocketchat-ui/client/views/app/modal.js
+++ b/packages/rocketchat-ui/client/views/app/modal.js
@@ -24,7 +24,7 @@ this.modal = {
this.config = config;
if (config.dontAskAgain) {
- const dontAskAgainList = RocketChat.getUserPreference(Meteor.user(), 'dontAskAgainList');
+ const dontAskAgainList = RocketChat.getUserPreference(Meteor.userId(), 'dontAskAgainList');
if (dontAskAgainList && dontAskAgainList.some((dontAsk) => dontAsk.action === config.dontAskAgain.action)) {
this.confirm(true);
@@ -66,7 +66,7 @@ this.modal = {
errorEl.style.display = 'block';
},
onKeydown(e) {
- if (e.key === 'Enter') {
+ if (e.key === 'Enter' && !/input|textarea|button/i.test(e.currentTarget.activeElement.tagName)) {
e.preventDefault();
e.stopPropagation();
diff --git a/packages/rocketchat-webdav/README.md b/packages/rocketchat-webdav/README.md
new file mode 100644
index 000000000000..920030805783
--- /dev/null
+++ b/packages/rocketchat-webdav/README.md
@@ -0,0 +1,3 @@
+# RocketChat WebDAV
+
+Package for RocketChat users to interact with WebDAV servers (Tested with ownCloud and Nextcloud).
diff --git a/packages/rocketchat-webdav/client/actionButton.js b/packages/rocketchat-webdav/client/actionButton.js
new file mode 100644
index 000000000000..b7c4ef80af34
--- /dev/null
+++ b/packages/rocketchat-webdav/client/actionButton.js
@@ -0,0 +1,45 @@
+/* globals modal, RocketChat*/
+
+Meteor.startup(function() {
+
+ RocketChat.MessageAction.addButton({
+ id: 'webdav-upload',
+ icon: 'upload',
+ label: t('Save_To_Webdav'),
+ condition: (message) => {
+ if (RocketChat.models.Subscriptions.findOne({ rid: message.rid }) == null) {
+ return false;
+ }
+ if (RocketChat.models.WebdavAccounts.findOne() == null) {
+ return false;
+ }
+ if (!message.file) {
+ return false;
+ }
+
+ return RocketChat.settings.get('Webdav_Integration_Enabled');
+ },
+ action() {
+ const [, message] = this._arguments;
+ const [attachment] = message.attachments;
+ const { file } = message;
+ const url = RocketChat.getURL(attachment.title_link, { full: true });
+ modal.open({
+ data: {
+ message,
+ attachment,
+ file,
+ url,
+ },
+ title: t('Save_To_Webdav'),
+ content: 'selectWebdavAccount',
+ showCancelButton: true,
+ showConfirmButton: false,
+ closeOnCancel: true,
+ html: true,
+ });
+ },
+ order: 100,
+ group: 'menu',
+ });
+});
diff --git a/packages/rocketchat-webdav/client/addWebdavAccount.html b/packages/rocketchat-webdav/client/addWebdavAccount.html
new file mode 100644
index 000000000000..567a1ba1dbda
--- /dev/null
+++ b/packages/rocketchat-webdav/client/addWebdavAccount.html
@@ -0,0 +1,43 @@
+
+
+
diff --git a/packages/rocketchat-webdav/client/addWebdavAccount.js b/packages/rocketchat-webdav/client/addWebdavAccount.js
new file mode 100644
index 000000000000..b066380f36cb
--- /dev/null
+++ b/packages/rocketchat-webdav/client/addWebdavAccount.js
@@ -0,0 +1,70 @@
+import _ from 'underscore';
+import toastr from 'toastr';
+
+Template.addWebdavAccount.helpers({
+ btnAddNewServer() {
+ if (Template.instance().loading.get()) {
+ return `${ t('Please_wait') }...`;
+ }
+ return t('Webdav_add_new_account');
+ },
+});
+
+Template.addWebdavAccount.events({
+ 'submit #add-webdav'(event, instance) {
+ event.preventDefault();
+ const formData = instance.validate();
+ instance.loading.set(true);
+ if (formData) {
+ Meteor.call('addWebdavAccount', formData, function(error, response) {
+ if (error) {
+ return toastr.error(t(error.error));
+ }
+ if (!response.success) {
+ return toastr.error(t(response.message));
+ }
+ toastr.success(t(response.message));
+ modal.close();
+ });
+ }
+ instance.loading.set(false);
+ },
+});
+
+const validate = function() {
+ const form = $(this.firstNode);
+ const formData = form.serializeArray();
+ const validationObj = {};
+
+ const formObj = formData.reduce((ret, { value, name }) => {
+ ret[name] = value;
+ return ret;
+ }, {});
+
+ if (!formObj.serverURL) {
+ validationObj.serverURL = t('Field_required');
+ }
+ if (!formObj.username) {
+ validationObj.username = t('Field_required');
+ }
+ if (!formObj.pass) {
+ validationObj.pass = t('Field_required');
+ }
+
+ form.find('input.error, select.error').removeClass('error');
+ form.find('.input-error').text('');
+ if (_.isEmpty(validationObj)) {
+ return formObj;
+ }
+ Object.entries(validationObj).forEach(([key, value]) => {
+ form.find(`input[name=${ key }], select[name=${ key }]`).addClass('error');
+ form.find(`input[name=${ key }]~.input-error, select[name=${ key }]~.input-error`).text(value);
+ });
+ this.loading.set(false);
+ return false;
+};
+
+Template.addWebdavAccount.onCreated(function() {
+ this.loading = new ReactiveVar(false);
+ this.validate = validate.bind(this);
+});
diff --git a/packages/rocketchat-webdav/client/collections/WebdavAccounts.js b/packages/rocketchat-webdav/client/collections/WebdavAccounts.js
new file mode 100644
index 000000000000..9844a1169235
--- /dev/null
+++ b/packages/rocketchat-webdav/client/collections/WebdavAccounts.js
@@ -0,0 +1 @@
+RocketChat.models.WebdavAccounts = new Mongo.Collection('rocketchat_webdav_accounts');
diff --git a/packages/rocketchat-webdav/client/selectWebdavAccount.html b/packages/rocketchat-webdav/client/selectWebdavAccount.html
new file mode 100644
index 000000000000..69b58eb2d909
--- /dev/null
+++ b/packages/rocketchat-webdav/client/selectWebdavAccount.html
@@ -0,0 +1,13 @@
+
+
+
diff --git a/packages/rocketchat-webdav/client/selectWebdavAccount.js b/packages/rocketchat-webdav/client/selectWebdavAccount.js
new file mode 100644
index 000000000000..bb4fcd979948
--- /dev/null
+++ b/packages/rocketchat-webdav/client/selectWebdavAccount.js
@@ -0,0 +1,39 @@
+/* global */
+import toastr from 'toastr';
+
+Template.selectWebdavAccount.helpers({
+ webdavAccounts() {
+ return RocketChat.models.WebdavAccounts.find().fetch();
+ },
+ usernamePlusServer(account) {
+ return account.name || `${ account.username }@${ account.server_url.replace(/^https?\:\/\//i, '') }`;
+ },
+});
+Template.selectWebdavAccount.events({
+ 'click .webdav-account'() {
+ modal.close();
+ const accountId = this._id;
+ const { url } = Template.instance().data;
+ const name = Template.instance().data.attachment.title;
+
+ const fileRequest = new XMLHttpRequest();
+ fileRequest.open('GET', url, true);
+ fileRequest.responseType = 'arraybuffer';
+ fileRequest.onload = function() {
+ const arrayBuffer = fileRequest.response;
+ if (arrayBuffer) {
+ const fileData = new Uint8Array(arrayBuffer);
+ Meteor.call('uploadFileToWebdav', accountId, fileData, name, (error, response) => {
+ if (error) {
+ return toastr.error(t(error.error));
+ }
+ if (!response.success) {
+ return toastr.error(t(response.message));
+ }
+ return toastr.success(t('File_uploaded'));
+ });
+ }
+ };
+ fileRequest.send(null);
+ },
+});
diff --git a/packages/rocketchat-webdav/client/webdavFilePicker.html b/packages/rocketchat-webdav/client/webdavFilePicker.html
new file mode 100644
index 000000000000..78507b90b555
--- /dev/null
+++ b/packages/rocketchat-webdav/client/webdavFilePicker.html
@@ -0,0 +1,29 @@
+
+
+
diff --git a/packages/rocketchat-webdav/client/webdavFilePicker.js b/packages/rocketchat-webdav/client/webdavFilePicker.js
new file mode 100644
index 000000000000..34da1203b8bf
--- /dev/null
+++ b/packages/rocketchat-webdav/client/webdavFilePicker.js
@@ -0,0 +1,196 @@
+/* global fileUploadHandler, Handlebars */
+import _ from 'underscore';
+import toastr from 'toastr';
+import { Session } from 'meteor/session';
+import { call } from 'meteor/rocketchat:lib';
+Template.webdavFilePicker.rendered = async function() {
+ const { accountId } = this.data;
+ Session.set('webdavCurrentFolder', '/');
+ const response = await call('getWebdavFileList', accountId, '/');
+ if (!response.success) {
+ modal.close();
+ return toastr.error(t(response.message));
+ }
+ Session.set('webdavNodes', response.data);
+};
+Template.webdavFilePicker.destroyed = function() {
+ Session.set('webdavNodes', []);
+};
+Template.webdavFilePicker.helpers({
+ iconType() {
+ // add icon for different types
+ let icon = 'file-generic';
+ let type = '';
+
+ let extension = this.basename.split('.').pop();
+ if (extension === this.basename) {
+ extension = '';
+ }
+
+ if (this.type === 'directory') {
+ icon = 'folder';
+ type = 'directory';
+ } else if (this.mime.match(/application\/pdf/)) {
+ icon = 'file-pdf';
+ type = 'ppt';
+ } else if (['application/vnd.oasis.opendocument.text', 'application/vnd.oasis.opendocument.presentation'].includes(this.mime)) {
+ icon = 'file-document';
+ type = 'document';
+ } else if (['application/vnd.ms-excel', 'application/vnd.oasis.opendocument.spreadsheet',
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'].includes(this.mime)) {
+ icon = 'file-sheets';
+ type = 'sheets';
+ } else if (['application/vnd.ms-powerpoint', 'application/vnd.oasis.opendocument.presentation'].includes(this.mime)) {
+ icon = 'file-sheets';
+ type = 'ppt';
+ }
+ return { icon, type, extension };
+ },
+ webdavNodes() {
+ return Session.get('webdavNodes');
+ },
+ webdavCurrentFolder() {
+ return Session.get('webdavCurrentFolder');
+ },
+});
+Template.webdavFilePicker.events({
+ async 'click #webdav-go-back'() {
+ const { accountId } = Template.instance().data;
+ let currentFolder = Session.get('webdavCurrentFolder');
+
+ // determine parent directory to go back
+ let parentFolder = '/';
+ if (currentFolder && currentFolder !== '/') {
+ if (currentFolder[currentFolder.length - 1] === '/') {
+ currentFolder = currentFolder.slice(0, -1);
+ }
+ parentFolder = currentFolder.substr(0, currentFolder.lastIndexOf('/') + 1);
+ }
+ Session.set('webdavCurrentFolder', parentFolder);
+ Session.set('webdavNodes', []);
+ const response = await call('getWebdavFileList', accountId, parentFolder);
+ if (!response.success) {
+ return toastr.error(t(response.message));
+ }
+ Session.set('webdavNodes', response.data);
+ },
+ async 'click .webdav_directory'() {
+ const { accountId } = Template.instance().data;
+ Session.set('webdavCurrentFolder', this.filename);
+ Session.set('webdavNodes', []);
+ const response = await call('getWebdavFileList', accountId, this.filename);
+ if (!response.success) {
+ modal.close();
+ return toastr.error(t(response.message));
+ }
+ Session.set('webdavNodes', response.data);
+ },
+ async 'click .webdav_file'() {
+ const roomId = Session.get('openedRoom');
+ const { accountId } = Template.instance().data;
+ const file = this;
+ const response = await call('getFileFromWebdav', accountId, file);
+
+ if (!response.success) {
+ modal.close();
+ return toastr.error(t('Failed_to_get_webdav_file'));
+ }
+ const blob = new Blob([response.data], { type: response.type });
+ // converting to file object
+ blob.lastModified = file.lastmod;
+ blob.name = file.basename;
+ const text = `
+`;
+
+ return modal.open({
+ title: t('Upload_file_question'),
+ text,
+ showCancelButton: true,
+ closeOnConfirm: false,
+ closeOnCancel: false,
+ confirmButtonText: t('Send'),
+ cancelButtonText: t('Cancel'),
+ html: true,
+ onRendered: () => $('#file-name').focus(),
+ }, function(isConfirm) {
+ const record = {
+ name: document.getElementById('file-name').value || blob.name,
+ size: blob.size,
+ type: blob.type,
+ rid: roomId,
+ description: document.getElementById('file-description').value,
+ };
+ modal.close();
+
+ if (!isConfirm) {
+ return;
+ }
+
+ const upload = fileUploadHandler('Uploads', record, blob);
+
+ let uploading = Session.get('uploading') || [];
+ uploading.push({
+ id: upload.id,
+ name: upload.getFileName(),
+ percentage: 0,
+ });
+
+ Session.set('uploading', uploading);
+
+ upload.onProgress = function(progress) {
+ uploading = Session.get('uploading');
+
+ const item = _.findWhere(uploading, { id: upload.id });
+ if (item != null) {
+ item.percentage = Math.round(progress * 100) || 0;
+ return Session.set('uploading', uploading);
+ }
+ };
+
+ upload.start(function(error, file, storage) {
+ if (error) {
+ let uploading = Session.get('uploading');
+ if (!Array.isArray(uploading)) {
+ uploading = [];
+ }
+
+ const item = _.findWhere(uploading, { id: upload.id });
+
+ if (_.isObject(item)) {
+ item.error = error.message;
+ item.percentage = 0;
+ } else {
+ uploading.push({
+ error: error.error,
+ percentage: 0,
+ });
+ }
+
+ Session.set('uploading', uploading);
+ return;
+ }
+
+ if (file) {
+ Meteor.call('sendFileMessage', roomId, storage, file, () => {
+ Meteor.setTimeout(() => {
+ const uploading = Session.get('uploading');
+ if (uploading !== null) {
+ const item = _.findWhere(uploading, {
+ id: upload.id,
+ });
+ return Session.set('uploading', _.without(uploading, item));
+ }
+ }, 2000);
+ });
+ }
+ });
+ });
+ },
+});
diff --git a/packages/rocketchat-webdav/package.js b/packages/rocketchat-webdav/package.js
new file mode 100644
index 000000000000..853358c5720b
--- /dev/null
+++ b/packages/rocketchat-webdav/package.js
@@ -0,0 +1,42 @@
+Package.describe({
+ name: 'rocketchat:webdav',
+ version: '0.0.1',
+
+ summary: 'Package for RocketChat users to interact with WebDAV servers.',
+
+ git: '',
+
+ documentation: 'README.md',
+});
+
+Package.onUse(function(api) {
+ api.use('ecmascript');
+ api.use('mongo');
+ api.use('templating');
+ api.use('less');
+ api.use('rocketchat:lib');
+ api.use('rocketchat:api');
+ api.use('rocketchat:grant');
+
+ api.addFiles('client/actionButton.js', 'client');
+ api.addFiles('client/addWebdavAccount.html', 'client');
+ api.addFiles('client/addWebdavAccount.js', 'client');
+ api.addFiles('client/webdavFilePicker.html', 'client');
+ api.addFiles('client/webdavFilePicker.js', 'client');
+ api.addFiles('client/selectWebdavAccount.html', 'client');
+ api.addFiles('client/selectWebdavAccount.js', 'client');
+
+ api.addFiles('client/collections/WebdavAccounts.js', 'client');
+
+ api.addFiles('server/methods/addWebdavAccount.js', 'server');
+ api.addFiles('server/methods/removeWebdavAccount.js', 'server');
+ api.addFiles('server/methods/getWebdavFileList.js', 'server');
+ api.addFiles('server/methods/getFileFromWebdav.js', 'server');
+ api.addFiles('server/methods/uploadFileToWebdav.js', 'server');
+ api.addFiles('server/models/WebdavAccounts.js', 'server');
+ api.addFiles('server/publications/webdavAccounts.js', 'server');
+
+ api.addFiles('startup/messageBoxActions.js', 'client');
+ api.addFiles('startup/subscription.js', 'client');
+ api.addFiles('startup/settings.js', 'server');
+});
diff --git a/packages/rocketchat-webdav/server/methods/addWebdavAccount.js b/packages/rocketchat-webdav/server/methods/addWebdavAccount.js
new file mode 100644
index 000000000000..b4f7a3af515e
--- /dev/null
+++ b/packages/rocketchat-webdav/server/methods/addWebdavAccount.js
@@ -0,0 +1,49 @@
+import Webdav from 'webdav';
+
+Meteor.methods({
+ async addWebdavAccount(formData) {
+
+ const userId = Meteor.userId();
+
+ if (!userId) {
+ throw new Meteor.Error('error-invalid-user', 'Invalid User', { method: 'addWebdavAccount' });
+ }
+
+ if (!RocketChat.settings.get('Webdav_Integration_Enabled')) {
+ throw new Meteor.Error('error-not-allowed', 'WebDAV Integration Not Allowed', { method: 'addWebdavAccount' });
+ }
+
+ check(formData, Match.ObjectIncluding({
+ serverURL: String,
+ username: String,
+ pass: String,
+ }));
+
+ const client = new Webdav(
+ formData.serverURL,
+ formData.username,
+ formData.pass
+ );
+
+ try {
+ await client.stat('/');
+ } catch (error) {
+ return { success: false, message: 'could-not-access-webdav', error };
+ }
+
+ const accountData = {
+ user_id: userId,
+ server_url: formData.serverURL,
+ username: formData.username,
+ password: formData.pass,
+ name: formData.name,
+ };
+ try {
+ RocketChat.models.WebdavAccounts.insert(accountData);
+ return { success: true, message: 'webdav-account-saved' };
+ } catch (error) {
+ return { success: false, message: error.code === 11000 ? 'duplicated-account' : 'unknown-write-error', error };
+ }
+
+ },
+});
diff --git a/packages/rocketchat-webdav/server/methods/getFileFromWebdav.js b/packages/rocketchat-webdav/server/methods/getFileFromWebdav.js
new file mode 100644
index 000000000000..3ad57fce1d3a
--- /dev/null
+++ b/packages/rocketchat-webdav/server/methods/getFileFromWebdav.js
@@ -0,0 +1,29 @@
+import Webdav from 'webdav';
+
+Meteor.methods({
+ async getFileFromWebdav(accountId, file) {
+ if (!Meteor.userId()) {
+ throw new Meteor.Error('error-invalid-user', 'Invalid User', { method: 'addNewWebdavAccount' });
+ }
+ if (!RocketChat.settings.get('Webdav_Integration_Enabled')) {
+ throw new Meteor.Error('error-not-allowed', 'WebDAV Integration Not Allowed', { method: 'addNewWebdavAccount' });
+ }
+
+ const account = RocketChat.models.WebdavAccounts.findOne({ _id: accountId, user_id: Meteor.userId() });
+ if (!account) {
+ throw new Meteor.Error('error-invalid-account', 'Invalid WebDAV Account', { method: 'addNewWebdavAccount' });
+ }
+ const client = new Webdav(
+ account.server_url,
+ account.username,
+ account.password
+ );
+ try {
+ const fileContent = await client.getFileContents(file.filename);
+ const data = new Uint8Array(fileContent);
+ return { success: true, data };
+ } catch (error) {
+ return { success: false, data: error };
+ }
+ },
+});
diff --git a/packages/rocketchat-webdav/server/methods/getWebdavFileList.js b/packages/rocketchat-webdav/server/methods/getWebdavFileList.js
new file mode 100644
index 000000000000..93c3dcea0d63
--- /dev/null
+++ b/packages/rocketchat-webdav/server/methods/getWebdavFileList.js
@@ -0,0 +1,30 @@
+import Webdav from 'webdav';
+
+Meteor.methods({
+ async getWebdavFileList(accountId, path) {
+ if (!Meteor.userId()) {
+ throw new Meteor.Error('error-invalid-user', 'Invalid User', { method: 'addNewWebdavAccount' });
+ }
+
+ if (!RocketChat.settings.get('Webdav_Integration_Enabled')) {
+ throw new Meteor.Error('error-not-allowed', 'WebDAV Integration Not Allowed', { method: 'addNewWebdavAccount' });
+ }
+
+ const account = RocketChat.models.WebdavAccounts.findOne({ _id: accountId, user_id: Meteor.userId() });
+ if (!account) {
+ throw new Meteor.Error('error-invalid-account', 'Invalid WebDAV Account', { method: 'addNewWebdavAccount' });
+ }
+
+ const client = new Webdav(
+ account.server_url,
+ account.username,
+ account.password
+ );
+ try {
+ const data = await client.getDirectoryContents(path);
+ return { success: true, data };
+ } catch (error) {
+ return { success: false, message: 'could-not-access-webdav', error };
+ }
+ },
+});
diff --git a/packages/rocketchat-webdav/server/methods/removeWebdavAccount.js b/packages/rocketchat-webdav/server/methods/removeWebdavAccount.js
new file mode 100644
index 000000000000..4cc461de072c
--- /dev/null
+++ b/packages/rocketchat-webdav/server/methods/removeWebdavAccount.js
@@ -0,0 +1,13 @@
+Meteor.methods({
+ removeWebdavAccount(accountId) {
+ if (!Meteor.userId()) {
+ throw new Meteor.Error('error-invalid-user', 'Invalid User', { method: 'removeWebdavAccount' });
+ }
+ // if (!RocketChat.settings.get('Webdav_Integration_Enabled')) {
+ // throw new Meteor.Error('error-not-allowed', 'WebDAV Integration Not Allowed', {method: 'removeWebdavAccount'});
+ // }
+ check(accountId, String);
+
+ return RocketChat.models.WebdavAccounts.removeByUserAndId(accountId, Meteor.userId());
+ },
+});
diff --git a/packages/rocketchat-webdav/server/methods/uploadFileToWebdav.js b/packages/rocketchat-webdav/server/methods/uploadFileToWebdav.js
new file mode 100644
index 000000000000..394cbc3bcbaa
--- /dev/null
+++ b/packages/rocketchat-webdav/server/methods/uploadFileToWebdav.js
@@ -0,0 +1,64 @@
+import Future from 'fibers/future';
+import Webdav from 'webdav';
+import stream from 'stream';
+
+Meteor.methods({
+ async uploadFileToWebdav(accountId, fileData, name) {
+ const uploadFolder = 'Rocket.Chat Uploads/';
+ if (!Meteor.userId()) {
+ throw new Meteor.Error('error-invalid-user', 'Invalid User', { method: 'uploadFileToWebdav' });
+ }
+ if (!RocketChat.settings.get('Webdav_Integration_Enabled')) {
+ throw new Meteor.Error('error-not-allowed', 'WebDAV Integration Not Allowed', { method: 'uploadFileToWebdav' });
+ }
+
+ const account = RocketChat.models.WebdavAccounts.findOne({ _id: accountId });
+ if (!account) {
+ throw new Meteor.Error('error-invalid-account', 'Invalid WebDAV Account', { method: 'uploadFileToWebdav' });
+ }
+ const client = new Webdav(
+ account.server_url,
+ account.username,
+ account.password
+ );
+ const future = new Future();
+
+ // create buffer stream from file data
+ let bufferStream = new stream.PassThrough();
+ if (fileData) {
+ bufferStream.end(fileData);
+ } else {
+ bufferStream = null;
+ }
+
+ // create a write stream on remote webdav server
+ const writeStream = client.createWriteStream(`${ uploadFolder }/${ name }`);
+ writeStream.on('end', function() {
+ future.return({ success: true });
+ });
+ writeStream.on('error', function() {
+ future.return({ success: false, message: 'FileUpload_Error' });
+ });
+
+ await client.stat(uploadFolder).then(function() {
+ bufferStream.pipe(writeStream);
+ }).catch(function(err) {
+ if (err.status === 404) {
+ client.createDirectory(uploadFolder).then(function() {
+ bufferStream.pipe(writeStream);
+ }).catch(function() {
+ if (err.status === 404) {
+ future.return({ success: false, message: 'webdav-server-not-found' });
+ } else {
+ future.return({ success: false, message: 'FileUpload_Error' });
+ }
+ });
+ } else if (err.status === 401) {
+ future.return({ success: false, message: 'error-invalid-account' });
+ } else {
+ future.return({ success: false, message: 'FileUpload_Error' });
+ }
+ });
+ return future.wait();
+ },
+});
diff --git a/packages/rocketchat-webdav/server/models/WebdavAccounts.js b/packages/rocketchat-webdav/server/models/WebdavAccounts.js
new file mode 100644
index 000000000000..0c4d5dba7bfa
--- /dev/null
+++ b/packages/rocketchat-webdav/server/models/WebdavAccounts.js
@@ -0,0 +1,26 @@
+/**
+ * Webdav Accounts model
+ */
+class WebdavAccounts extends RocketChat.models._Base {
+ constructor() {
+ super('webdav_accounts');
+
+ this.tryEnsureIndex({ user_id: 1, server_url: 1, username: 1, name: 1 }, { unique: 1 });
+ }
+
+ findWithUserId(user_id, options) {
+ const query = { user_id };
+ return this.find(query, options);
+ }
+
+ removeByUserAndId(_id, user_id) {
+ return this.remove({ _id, user_id });
+ }
+
+ removeById(_id) {
+ return this.remove({ _id });
+ }
+
+}
+
+RocketChat.models.WebdavAccounts = new WebdavAccounts();
diff --git a/packages/rocketchat-webdav/server/publications/webdavAccounts.js b/packages/rocketchat-webdav/server/publications/webdavAccounts.js
new file mode 100644
index 000000000000..40723b452c64
--- /dev/null
+++ b/packages/rocketchat-webdav/server/publications/webdavAccounts.js
@@ -0,0 +1,14 @@
+Meteor.publish('webdavAccounts', function() {
+ if (!this.userId) {
+ return this.error(new Meteor.Error('error-not-authorized', 'Not authorized', { publish: 'webdavAccounts' }));
+ }
+
+ return RocketChat.models.WebdavAccounts.findWithUserId(this.userId, {
+ fields: {
+ _id:1,
+ username: 1,
+ server_url: 1,
+ name: 1,
+ },
+ });
+});
diff --git a/packages/rocketchat-webdav/startup/messageBoxActions.js b/packages/rocketchat-webdav/startup/messageBoxActions.js
new file mode 100644
index 000000000000..75dc81dc280f
--- /dev/null
+++ b/packages/rocketchat-webdav/startup/messageBoxActions.js
@@ -0,0 +1,55 @@
+/* globals modal, RocketChat */
+RocketChat.messageBox.actions.add('WebDAV', 'Add Server', {
+ id: 'add-webdav',
+ icon: 'plus',
+ condition: () => RocketChat.settings.get('Webdav_Integration_Enabled'),
+ action() {
+ modal.open({
+ title: t('Webdav_add_new_account'),
+ content: 'addWebdavAccount',
+ showCancelButton: false,
+ showConfirmButton: false,
+ showFooter: false,
+ closeOnCancel: true,
+ html: true,
+ });
+ },
+});
+
+Meteor.startup(function() {
+ Tracker.autorun(() => {
+ const accounts = RocketChat.models.WebdavAccounts.find();
+
+
+ if (accounts.count() === 0) {
+ return RocketChat.messageBox.actions.remove(/webdav-upload-/ig);
+ }
+
+ accounts.forEach((account) => {
+ const name = account.name || `${ account.username }@${ account.server_url.replace(/^https?\:\/\//i, '') }`;
+ const title = t('Upload_From', {
+ name,
+ });
+ RocketChat.messageBox.actions.add('WebDAV', name, {
+ id: `webdav-upload-${ account._id.toLowerCase() }`,
+ icon: 'cloud-plus',
+ condition: () => RocketChat.settings.get('Webdav_Integration_Enabled'),
+ action() {
+ modal.open({
+ data: {
+ name,
+ accountId: account._id,
+ },
+ title,
+ content: 'webdavFilePicker',
+ showCancelButton: true,
+ showFooter: false,
+ showConfirmButton: false,
+ closeOnCancel: true,
+ html: true,
+ });
+ },
+ });
+ });
+ });
+});
diff --git a/packages/rocketchat-webdav/startup/settings.js b/packages/rocketchat-webdav/startup/settings.js
new file mode 100644
index 000000000000..feac17852f4d
--- /dev/null
+++ b/packages/rocketchat-webdav/startup/settings.js
@@ -0,0 +1,6 @@
+RocketChat.settings.addGroup('Webdav Integration', function() {
+ this.add('Webdav_Integration_Enabled', false, {
+ type: 'boolean',
+ public: true,
+ });
+});
diff --git a/packages/rocketchat-webdav/startup/subscription.js b/packages/rocketchat-webdav/startup/subscription.js
new file mode 100644
index 000000000000..0f6247066b38
--- /dev/null
+++ b/packages/rocketchat-webdav/startup/subscription.js
@@ -0,0 +1,6 @@
+import { Tracker } from 'meteor/tracker';
+Tracker.autorun(() => {
+ if (Meteor.userId()) {
+ Meteor.subscribe('webdavAccounts');
+ }
+});