From 28766b8804d1a5b980d7e2ab842e626108f65c55 Mon Sep 17 00:00:00 2001 From: Ben Gotow Date: Wed, 5 Jul 2017 12:57:05 -0700 Subject: [PATCH] Initial support for multisend --- packages/client-app/spec/isomorphic-core | 1 - .../spec/tasks/change-mail-task-spec.coffee | 1 - .../client-app/src/flux/mailsync-bridge.es6 | 12 +- .../src/flux/stores/draft-factory.coffee | 2 +- .../src/flux/stores/draft-store.es6 | 16 +- .../ensure-message-in-sent-folder-task.es6 | 27 ---- .../src/flux/tasks/send-draft-task.es6 | 137 +++++------------- packages/client-app/src/flux/tasks/task.es6 | 11 ++ .../client-app/src/global/nylas-exports.es6 | 2 - 9 files changed, 67 insertions(+), 142 deletions(-) delete mode 120000 packages/client-app/spec/isomorphic-core delete mode 100644 packages/client-app/src/flux/tasks/ensure-message-in-sent-folder-task.es6 diff --git a/packages/client-app/spec/isomorphic-core b/packages/client-app/spec/isomorphic-core deleted file mode 120000 index 7ba4a6f91..000000000 --- a/packages/client-app/spec/isomorphic-core +++ /dev/null @@ -1 +0,0 @@ -/Users/bengotow/Work/F376/Projects/Nylas2/nylas-mail/packages/isomorphic-core/spec \ No newline at end of file diff --git a/packages/client-app/spec/tasks/change-mail-task-spec.coffee b/packages/client-app/spec/tasks/change-mail-task-spec.coffee index a5bf5dac5..2595c9621 100644 --- a/packages/client-app/spec/tasks/change-mail-task-spec.coffee +++ b/packages/client-app/spec/tasks/change-mail-task-spec.coffee @@ -13,7 +13,6 @@ _ = require 'underscore' Task, Utils, ChangeMailTask, - EnsureMessageInSentFolderTask, } = require 'nylas-exports' xdescribe "ChangeMailTask", -> diff --git a/packages/client-app/src/flux/mailsync-bridge.es6 b/packages/client-app/src/flux/mailsync-bridge.es6 index ef0f839d2..2235ccaaf 100644 --- a/packages/client-app/src/flux/mailsync-bridge.es6 +++ b/packages/client-app/src/flux/mailsync-bridge.es6 @@ -6,6 +6,7 @@ import Actions from './actions'; import Utils from './models/utils'; let AccountStore = null; +let Task = null; export default class MailsyncBridge { constructor() { @@ -21,7 +22,8 @@ export default class MailsyncBridge { this.clients = {}; - AccountStore = require('./stores/account-store').default; + Task = require('./tasks/task').default; //eslint-disable-line + AccountStore = require('./stores/account-store').default; //eslint-disable-line AccountStore.listen(this.ensureClients, this); this.ensureClients(); @@ -102,6 +104,14 @@ export default class MailsyncBridge { DatabaseStore.triggeringFromActionBridge = true; DatabaseStore.trigger(new DatabaseChangeRecord({type, objectClass, objects: [object]})); DatabaseStore.triggeringFromActionBridge = false; + + if (object instanceof Task && object.status === 'complete') { + if (object.error != null) { + object.onError(object.error); + } else { + object.onSuccess(); + } + } } } diff --git a/packages/client-app/src/flux/stores/draft-factory.coffee b/packages/client-app/src/flux/stores/draft-factory.coffee index 35039ccc9..ff03fdecc 100644 --- a/packages/client-app/src/flux/stores/draft-factory.coffee +++ b/packages/client-app/src/flux/stores/draft-factory.coffee @@ -31,7 +31,7 @@ class DraftFactory unread: false starred: false folderUID: 0 - headerMessageId: Utils.generateTempId() + headerMessageId: Utils.generateTempId() + "@" + require('os').hostname() from: [account.defaultMe()] date: (new Date) draft: true diff --git a/packages/client-app/src/flux/stores/draft-store.es6 b/packages/client-app/src/flux/stores/draft-store.es6 index cbd2f4765..b8daaad3f 100644 --- a/packages/client-app/src/flux/stores/draft-store.es6 +++ b/packages/client-app/src/flux/stores/draft-store.es6 @@ -413,19 +413,15 @@ class DraftStore extends NylasStore { // We also need to delay because the old draft window needs to fully // close. It takes windows currently (June 2016) 100ms to close by setTimeout(() => { - this._notifyUserOfError({headerMessageId, threadId, errorMessage, errorDetail}); + const focusedThread = FocusedContentStore.focused('thread'); + if (threadId && focusedThread && focusedThread.id === threadId) { + NylasEnv.showErrorDialog(errorMessage, {detail: errorDetail}); + } else { + Actions.composePopoutDraft(headerMessageId, {errorMessage, errorDetail}); + } }, 300); } } - - _notifyUserOfError({headerMessageId, threadId, errorMessage, errorDetail}) { - const focusedThread = FocusedContentStore.focused('thread'); - if (threadId && focusedThread && focusedThread.id === threadId) { - NylasEnv.showErrorDialog(errorMessage, {detail: errorDetail}); - } else { - Actions.composePopoutDraft(headerMessageId, {errorMessage, errorDetail}); - } - } } export default new DraftStore(); diff --git a/packages/client-app/src/flux/tasks/ensure-message-in-sent-folder-task.es6 b/packages/client-app/src/flux/tasks/ensure-message-in-sent-folder-task.es6 deleted file mode 100644 index a84cd441b..000000000 --- a/packages/client-app/src/flux/tasks/ensure-message-in-sent-folder-task.es6 +++ /dev/null @@ -1,27 +0,0 @@ -import Task from './task'; -import SendDraftTask from './send-draft-task'; - - -export default class EnsureMessageInSentFolderTask extends Task { - constructor(opts = {}) { - super(opts); - this.message = opts.message; - this.customSentMessage = opts.customSentMessage; - } - - label() { - return "Saving to sent folder"; - } - - isDependentOnTask(other) { - return (other instanceof SendDraftTask) && (other.message) && (other.message.id === this.message.id); - } - - performLocal() { - if (!this.message) { - const errMsg = `Attempt to call ${this.constructor.name}.performLocal without a message`; - return Promise.reject(new Error(errMsg)); - } - return Promise.resolve(); - } -} diff --git a/packages/client-app/src/flux/tasks/send-draft-task.es6 b/packages/client-app/src/flux/tasks/send-draft-task.es6 index 5f62d15d9..6b5aff8a8 100644 --- a/packages/client-app/src/flux/tasks/send-draft-task.es6 +++ b/packages/client-app/src/flux/tasks/send-draft-task.es6 @@ -1,6 +1,8 @@ /* eslint global-require: 0 */ import AccountStore from '../stores/account-store'; import Task from './task'; +import Actions from '../actions'; +import SoundRegistry from '../../registries/sound-registry'; const OPEN_TRACKING_ID = NylasEnv.packages.pluginIdFor('open-tracking') const LINK_TRACKING_ID = NylasEnv.packages.pluginIdFor('link-tracking') @@ -17,132 +19,69 @@ export default class SendDraftTask extends Task { this.emitError = emitError this.playSound = playSound this.allowMultiSend = allowMultiSend + + if (draft) { + // const pluginsAvailable = (OPEN_TRACKING_ID && LINK_TRACKING_ID); + // const pluginsInUse = pluginsAvailable && (!!this.draft.metadataForPluginId(OPEN_TRACKING_ID) || !!this.draft.metadataForPluginId(LINK_TRACKING_ID)); + // if (pluginsInUse) { + this.perRecipientBodies = { + self: draft.body, + }; + // perform transformations here + const ps = draft.participants({includeFrom: false, includeBcc: true}); + ps.forEach((p) => { + this.perRecipientBodies[p.email] = draft.body + p.email; + }) + // } + } } label() { return "Sending message"; } - assertDraftValidity = () => { - if (!this.draft.from[0]) { - return Promise.reject(new Error("SendDraftTask - you must populate `from` before sending.")); - } - + validate() { const account = AccountStore.accountForEmail(this.draft.from[0].email); + + if (!this.draft.from[0]) { + throw new Error("SendDraftTask - you must populate `from` before sending."); + } if (!account) { - return Promise.reject(new Error("SendDraftTask - you can only send drafts from a configured account.")); + throw new Error("SendDraftTask - you can only send drafts from a configured account."); } if (this.draft.accountId !== account.id) { - return Promise.reject(new Error("The from address has changed since you started sending this draft. Double-check the draft and click 'Send' again.")); + throw new Error("The from address has changed since you started sending this draft. Double-check the draft and click 'Send' again."); } - return Promise.resolve(); } - _trackingPluginsInUse() { - const pluginsAvailable = (OPEN_TRACKING_ID && LINK_TRACKING_ID); - if (!pluginsAvailable) { - return false; - } - return (!!this.draft.metadataForPluginId(OPEN_TRACKING_ID) || !!this.draft.metadataForPluginId(LINK_TRACKING_ID)) || false; - } - - _createMessageFromResponse = (responseJSON) => { - const {failedRecipients, message} = responseJSON - if (failedRecipients && failedRecipients.length > 0) { - const errorMessage = `We had trouble sending this message to all recipients. ${failedRecipients} may not have received this email.`; - NylasEnv.showErrorDialog(errorMessage, {showInMainWindow: true}); - } - if (!message || !message.id || !message.account_id) { - const errorMessage = `Your message successfully sent; however, we had trouble saving your message, "${message.subject}", to your Sent folder.`; - if (!message) { - throw new Error(`${errorMessage}\n\nError: Did not return message`) - } - if (!message.id) { - throw new Error(`${errorMessage}\n\nError: Returned a message without id`) - } - if (!message.accountId) { - throw new Error(`${errorMessage}\n\nError: Returned a message without accountId`) - } - } - - this.message = new Message().fromJSON(message); - this.message.id = this.draft.id; - this.message.body = this.draft.body; - this.message.draft = false; - this.message.clonePluginMetadataFrom(this.draft); - - return DatabaseStore.inTransaction((t) => - this.refreshDraftReference().then(() => { - return t.persistModel(this.message); - }) - ); - } - - onSuccess = () => { + onSuccess() { Actions.recordUserEvent("Draft Sent") - Actions.draftDeliverySucceeded({message: this.message, messageId: this.message.id, headerMessageId: this.draft.headerMessageId}); + Actions.draftDeliverySucceeded({headerMessageId: this.draft.headerMessageId}); // Play the sending sound if (this.playSound && NylasEnv.config.get("core.sending.sounds")) { SoundRegistry.playSound('send'); } - return Promise.resolve(Task.Status.Success); } - onError = (err) => { - let message = err.message; - - // TODO Handle errors in a cleaner way - if (err instanceof APIError) { - const errorMessage = (err.body && err.body.message) || '' - message = `Sorry, this message could not be sent, please try again.`; - message += `\n\nReason: ${err.message}` - if (errorMessage.includes('unable to reach your SMTP server')) { - message = `Sorry, this message could not be sent. There was a network error, please make sure you are online.` - } - if (errorMessage.includes('Incorrect SMTP username or password') || - errorMessage.includes('SMTP protocol error') || - errorMessage.includes('unable to look up your SMTP host')) { - Actions.updateAccount(this.draft.accountId, {syncState: Account.SYNC_STATE_AUTH_FAILED}) - message = `Sorry, this message could not be sent due to an authentication error. Please re-authenticate your account and try again.` - } - if (err.statusCode === 402) { - if (errorMessage.includes('at least one recipient')) { - message = `This message could not be delivered to at least one recipient. (Note: other recipients may have received this message - you should check Sent Mail before re-sending this message.)`; - } else { - message = `Sorry, this message could not be sent because it was rejected by your mail provider. (${errorMessage})`; - if (err.body.server_error) { - message += `\n\n${err.body.server_error}`; - } - } - } + onError({key, debuginfo}) { + let message = key; + if (key === 'no-sent-folder') { + message = "We couldn't find a Sent folder in your account."; } if (this.emitError) { - if (err instanceof RequestEnsureOnceError) { - Actions.draftDeliveryFailed({ - threadId: this.draft.threadId, - headerMessageId: this.draft.headerMessageId, - errorMessage: `WARNING: Your message MIGHT have sent. We encountered a network problem while the send was in progress. Please wait a few minutes then check your sent folder and try again if necessary.`, - errorDetail: `Please email support@nylas.com if you see this error message.`, - }); - } else { - Actions.draftDeliveryFailed({ - threadId: this.draft.threadId, - headerMessageId: this.draft.headerMessageId, - errorMessage: message, - errorDetail: err.message + (err.error ? err.error.stack : '') + err.stack, - }); - } + Actions.draftDeliveryFailed({ + threadId: this.draft.threadId, + headerMessageId: this.draft.headerMessageId, + errorMessage: message, + errorDetail: debuginfo, + }); } Actions.recordUserEvent("Draft Sending Errored", { - error: err.message, - errorClass: err.constructor.name, + error: message, + key: key, }) - err.message = `Send failed (client): ${err.message}` - NylasEnv.reportError(err); - - return Promise.resolve([Task.Status.Failed, err]); } } diff --git a/packages/client-app/src/flux/tasks/task.es6 b/packages/client-app/src/flux/tasks/task.es6 index 52372309c..f334c8cd1 100644 --- a/packages/client-app/src/flux/tasks/task.es6 +++ b/packages/client-app/src/flux/tasks/task.es6 @@ -27,6 +27,9 @@ export default class Task extends Model { queryable: true, modelKey: 'status', }), + error: Attributes.Object({ + modelKey: 'error', + }), }); // Public: Override the constructor to pass initial args to your Task and @@ -116,4 +119,12 @@ export default class Task extends Model { } return this; } + + onError(err) { + // noop + } + + onSuccess() { + // noop + } } diff --git a/packages/client-app/src/global/nylas-exports.es6 b/packages/client-app/src/global/nylas-exports.es6 index 6dab8aff0..7e0803415 100644 --- a/packages/client-app/src/global/nylas-exports.es6 +++ b/packages/client-app/src/global/nylas-exports.es6 @@ -100,7 +100,6 @@ lazyLoad(`IMAPSearchQueryBackend`, 'services/search/search-query-backend-imap'); lazyLoad(`TaskFactory`, 'flux/tasks/task-factory'); lazyLoadAndRegisterTask(`Task`, 'task'); lazyLoadAndRegisterTask(`EventRSVPTask`, 'event-rsvp-task'); -lazyLoadAndRegisterTask(`BaseDraftTask`, 'base-draft-task'); lazyLoadAndRegisterTask(`SendDraftTask`, 'send-draft-task'); lazyLoadAndRegisterTask(`ChangeMailTask`, 'change-mail-task'); lazyLoadAndRegisterTask(`DestroyDraftTask`, 'destroy-draft-task'); @@ -117,7 +116,6 @@ lazyLoadAndRegisterTask(`SyncbackCategoryTask`, 'syncback-category-task'); lazyLoadAndRegisterTask(`SyncbackMetadataTask`, 'syncback-metadata-task'); lazyLoadAndRegisterTask(`ReprocessMailRulesTask`, 'reprocess-mail-rules-task'); lazyLoadAndRegisterTask(`SendFeatureUsageEventTask`, 'send-feature-usage-event-task'); -lazyLoadAndRegisterTask(`EnsureMessageInSentFolderTask`, 'ensure-message-in-sent-folder-task'); // Stores // These need to be required immediately since some Stores are