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-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 62ad563f91f3..34572f6b0f0d 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -592,6 +592,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", @@ -971,6 +972,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", @@ -1065,6 +1067,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.", @@ -2279,6 +2282,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", "Saved": "Saved", "Saving": "Saving", "Scan_QR_code": "Using an authenticator app like Google Authenticator, Authy or Duo, scan the QR code. It will display a 6 digit code which you need to enter below.", @@ -2658,7 +2662,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", @@ -2826,6 +2832,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-livechat/.app/package-lock.json b/packages/rocketchat-livechat/.app/package-lock.json index 41b97562bafa..bbfd1e1275a2 100644 --- a/packages/rocketchat-livechat/.app/package-lock.json +++ b/packages/rocketchat-livechat/.app/package-lock.json @@ -5,12 +5,12 @@ "requires": true, "dependencies": { "@babel/runtime": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0-beta.51.tgz", - "integrity": "sha1-SLjtGDBwNMZiD2Q1FGUMoszAFlo=", + "version": "7.0.0-beta.52", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0-beta.52.tgz", + "integrity": "sha1-PztCuCuStOGig/x43xuy/Uuo0Mc=", "requires": { "core-js": "2.5.7", - "regenerator-runtime": "0.11.1" + "regenerator-runtime": "0.12.0" } }, "autolinker": { @@ -1279,9 +1279,9 @@ } }, "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.0.tgz", + "integrity": "sha512-SpV2LhF5Dm9UYMEprB3WwsBnWwqTrmjrm2UZb42cl2G02WVGgx7Mg8aa9pdLEKp6hZ+/abcMc2NxKA8f02EG2w==" }, "sprintf-js": { "version": "1.1.1", @@ -1289,9 +1289,9 @@ "integrity": "sha1-Nr54Mgr+WAH2zqPueLblqrlA6gw=" }, "sweetalert2": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-7.25.4.tgz", - "integrity": "sha512-FC4acFtZ+ykFGkRhf/+sZffnPXyMGsXRhz870UqBPCLnrPpaiiQS0WhewCnERWZF/aLtJLB+bfcPh2VsQemUBw==" + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-7.25.0.tgz", + "integrity": "sha512-RwwhSnnLnTWYug/a7rDV6yWee/E6s13c2qkn61Flw6/ZUauIxcbKXz1bTdb/LBzykumI+bjwsJCLgEaFq6t6WA==" }, "toastr": { "version": "2.1.4", diff --git a/packages/rocketchat-ui-account/client/accountFlex.html b/packages/rocketchat-ui-account/client/accountFlex.html index 68a5d6209fa0..3b3b9f5f39c7 100644 --- a/packages/rocketchat-ui-account/client/accountFlex.html +++ b/packages/rocketchat-ui-account/client/accountFlex.html @@ -18,9 +18,13 @@

{{_ "My_Account"}}

{{> sidebarItem menuItem "Security" "lock" "account" "security" }} + + {{> 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 @@ + 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..b6f67dcb62e1 100644 --- a/packages/rocketchat-ui-account/package.js +++ b/packages/rocketchat-ui-account/package.js @@ -21,13 +21,16 @@ 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'); api.addFiles('client/avatar/prompt.html', 'client'); 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 3a6087813d0b..d84e520bec9c 100644 --- a/packages/rocketchat-ui/client/views/app/modal.js +++ b/packages/rocketchat-ui/client/views/app/modal.js @@ -23,7 +23,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); @@ -61,7 +61,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..7b908efa8092 --- /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 = Meteor.absoluteUrl().concat(attachment.title_link.substring(1)); + 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..5b5e38d3c7db --- /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..069edf027bfd --- /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('Success_Webdav_Upload')); + }); + } + }; + 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..fe1c07b741be --- /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..1c8ec59fd3fb --- /dev/null +++ b/packages/rocketchat-webdav/client/webdavFilePicker.js @@ -0,0 +1,199 @@ +/* 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 }; + }, + equals(a, b) { + return a === b; + }, + 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..1e0607dbdabf --- /dev/null +++ b/packages/rocketchat-webdav/startup/settings.js @@ -0,0 +1,6 @@ +RocketChat.settings.addGroup('WebdavIntegration', 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'); + } +});