Initial support for multisend

This commit is contained in:
Ben Gotow 2017-07-05 12:57:05 -07:00
parent da2dbe43c9
commit 28766b8804
9 changed files with 67 additions and 142 deletions

View file

@ -1 +0,0 @@
/Users/bengotow/Work/F376/Projects/Nylas2/nylas-mail/packages/isomorphic-core/spec

View file

@ -13,7 +13,6 @@ _ = require 'underscore'
Task,
Utils,
ChangeMailTask,
EnsureMessageInSentFolderTask,
} = require 'nylas-exports'
xdescribe "ChangeMailTask", ->

View file

@ -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();
}
}
}
}

View file

@ -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

View file

@ -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();

View file

@ -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();
}
}

View file

@ -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]);
}
}

View file

@ -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
}
}

View file

@ -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