mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-09-22 16:26:08 +08:00
Revert "[feat] Add support for send later"
This reverts commit 683a550d49
.
This commit is contained in:
parent
683a550d49
commit
2f67d8ac8b
|
@ -1,4 +1,4 @@
|
|||
const {parseFromImap, parseSnippet, parseContacts} = require('../src/message-factory');
|
||||
const {parseFromImap, parseSnippet, parseContacts} = require('../../src/shared/message-factory');
|
||||
const {forEachJSONFixture, forEachHTMLAndTXTFixture, ACCOUNT_ID, getTestDatabase} = require('../helpers');
|
||||
|
||||
xdescribe('MessageFactory', function MessageFactorySpecs() {
|
|
@ -1,8 +1,6 @@
|
|||
const {MessageFactory, Errors: {APIError}} = require('isomorphic-core')
|
||||
const Joi = require('joi');
|
||||
const crypto = require('crypto');
|
||||
|
||||
// TODO: This is a placeholder.
|
||||
// TODO: This is a placeholder
|
||||
module.exports = (server) => {
|
||||
server.route({
|
||||
method: 'GET',
|
||||
|
@ -26,79 +24,4 @@ module.exports = (server) => {
|
|||
reply('[]');
|
||||
},
|
||||
});
|
||||
|
||||
// This is a placeholder route we use to make send-later happy.
|
||||
// Eventually, we should flesh it out and actually sync back drafts.
|
||||
server.route({
|
||||
method: ['PUT', 'POST'],
|
||||
path: `/drafts/{objectId?}`,
|
||||
config: {
|
||||
description: `Dummy draft update`,
|
||||
tags: ['drafts'],
|
||||
payload: {
|
||||
output: 'data',
|
||||
parse: true,
|
||||
},
|
||||
validate: {
|
||||
params: {
|
||||
objectId: Joi.string(),
|
||||
},
|
||||
},
|
||||
},
|
||||
handler: (request, reply) => {
|
||||
const data = request.payload;
|
||||
data.id = crypto.createHash('sha256').update(data.client_id, 'utf8').digest('hex')
|
||||
return reply(data);
|
||||
},
|
||||
})
|
||||
|
||||
server.route({
|
||||
method: ['PUT', 'POST'],
|
||||
path: `/drafts/build`,
|
||||
config: {
|
||||
description: `Returns a ready-made draft message. Used by our send later plugin.`,
|
||||
tags: ['drafts'],
|
||||
payload: {
|
||||
output: 'data',
|
||||
parse: true,
|
||||
},
|
||||
},
|
||||
handler: async (request, reply) => {
|
||||
const db = await request.getAccountDatabase();
|
||||
const account = request.auth.credentials;
|
||||
|
||||
let sentFolderName;
|
||||
let sentFolder;
|
||||
let trashFolderName;
|
||||
let trashFolder;
|
||||
|
||||
if (account.provider === 'gmail') {
|
||||
sentFolder = await db.Label.find({where: {role: 'sent'}});
|
||||
} else {
|
||||
sentFolder = await db.Folder.find({where: {role: 'sent'}});
|
||||
}
|
||||
|
||||
if (sentFolder) {
|
||||
sentFolderName = sentFolder.name;
|
||||
} else {
|
||||
throw new APIError(`Can't process draft for send later`, 500);
|
||||
}
|
||||
|
||||
if (account.provider === 'gmail') {
|
||||
trashFolder = await db.Label.find({where: {role: 'trash'}});
|
||||
} else {
|
||||
trashFolder = await db.Folder.find({where: {role: 'trash'}});
|
||||
}
|
||||
|
||||
if (trashFolder) {
|
||||
trashFolderName = trashFolder.name;
|
||||
} else {
|
||||
throw new APIError(`Can't process draft for send later`, 500);
|
||||
}
|
||||
|
||||
const message = await MessageFactory.buildForSend(db, request.payload);
|
||||
const ret = Object.assign(message.toJSON(), { sentFolderName, trashFolderName });
|
||||
reply(JSON.stringify(ret));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const Joi = require('joi');
|
||||
const {SendUtils} = require('isomorphic-core');
|
||||
const Utils = require('../../shared/utils');
|
||||
const {createAndReplyWithSyncbackRequest} = require('../route-helpers');
|
||||
|
||||
|
||||
|
@ -70,7 +70,7 @@ module.exports = (server) => {
|
|||
const {messageId} = request.params;
|
||||
const {sentPerRecipient} = request.payload;
|
||||
|
||||
if (!SendUtils.isValidId(messageId)) {
|
||||
if (!Utils.isValidId(messageId)) {
|
||||
reply.badRequest(`messageId is not a base-36 integer`)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const {SendmailClient, Provider,
|
||||
Errors: {APIError}, MessageFactory: {getReplyHeaders}} = require('isomorphic-core')
|
||||
const {SendmailClient, Provider, Errors: {APIError}} = require('isomorphic-core')
|
||||
const {SyncbackIMAPTask} = require('./syncback-task')
|
||||
const SyncTaskFactory = require('../sync-task-factory');
|
||||
const {getReplyHeaders} = require('../../shared/message-factory')
|
||||
|
||||
|
||||
async function deleteGmailSentMessages({db, imap, provider, headerMessageId}) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
const {Errors: {APIError}, SendUtils} = require('isomorphic-core')
|
||||
const {Errors: {APIError}} = require('isomorphic-core')
|
||||
const Utils = require('../../shared/utils')
|
||||
const {SyncbackSMTPTask} = require('./syncback-task')
|
||||
const {MessageFactory} = require('isomorphic-core')
|
||||
const MessageFactory = require('../../shared/message-factory')
|
||||
|
||||
|
||||
/**
|
||||
|
@ -35,14 +36,7 @@ class SendMessagePerRecipientSMTP extends SyncbackSMTPTask {
|
|||
await syncbackRequest.update({
|
||||
status: 'INPROGRESS-NOTRETRYABLE',
|
||||
})
|
||||
|
||||
let sendResult;
|
||||
try {
|
||||
sendResult = await this._sendPerRecipient({
|
||||
db, smtp, baseMessage, logger: this._logger, usesOpenTracking, usesLinkTracking})
|
||||
} catch (err) {
|
||||
throw new APIError('SendMessagePerRecipient: Sending failed for all recipients', 500);
|
||||
}
|
||||
const sendResult = await this._sendPerRecipient({db, smtp, baseMessage, usesOpenTracking, usesLinkTracking})
|
||||
/**
|
||||
* Once messages have actually been delivered, we need to be very
|
||||
* careful not to throw an error from this task. An Error in the send
|
||||
|
@ -85,7 +79,7 @@ class SendMessagePerRecipientSMTP extends SyncbackSMTPTask {
|
|||
usesLinkTracking,
|
||||
})
|
||||
|
||||
const individualizedMessage = SendUtils.copyModel(Message, baseMessage, {
|
||||
const individualizedMessage = Utils.copyModel(Message, baseMessage, {
|
||||
body: customBody,
|
||||
})
|
||||
// TODO we set these temporary properties which aren't stored in the
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const {MessageFactory} = require('isomorphic-core')
|
||||
const MessageFactory = require('../../shared/message-factory')
|
||||
const {SyncbackSMTPTask} = require('../syncback-tasks/syncback-task')
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,7 +6,7 @@ const mkdirp = require('mkdirp');
|
|||
const detectThread = require('./detect-thread');
|
||||
const extractFiles = require('./extract-files');
|
||||
const extractContacts = require('./extract-contacts');
|
||||
const {MessageFactory} = require('isomorphic-core');
|
||||
const MessageFactory = require('../shared/message-factory')
|
||||
const LocalDatabaseConnector = require('../shared/local-database-connector');
|
||||
const {BatteryStatusManager} = require('nylas-exports');
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
/* eslint no-useless-escape: 0 */
|
||||
import _ from 'underscore';
|
||||
const mimelib = require('mimelib');
|
||||
const encoding = require('encoding');
|
||||
const he = require('he');
|
||||
|
@ -7,29 +6,13 @@ const os = require('os');
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const mkdirp = require('mkdirp');
|
||||
const btoa = require('btoa')
|
||||
const {APIError} = require('./errors');
|
||||
const {deepClone} = require('./send-utils');
|
||||
const {Errors: {APIError}} = require('isomorphic-core');
|
||||
const {N1CloudAPI, RegExpUtils, Utils} = require('nylas-exports');
|
||||
|
||||
// Aiming for the former in length, but the latter is the hard db cutoff
|
||||
const SNIPPET_SIZE = 100;
|
||||
const SNIPPET_MAX_SIZE = 255;
|
||||
|
||||
// Copied from regexp-utils.coffee.
|
||||
// FIXME @karim: share code, somehow.
|
||||
// Test cases: https://regex101.com/r/cK0zD8/4
|
||||
// Catches link tags containing which are:
|
||||
// - Non empty
|
||||
// - Not a mailto: link
|
||||
// Returns the following capturing groups:
|
||||
// 1. start of the opening a tag to href="
|
||||
// 2. The contents of the href without quotes
|
||||
// 3. the rest of the opening a tag
|
||||
// 4. the contents of the a tag
|
||||
// 5. the closing tag
|
||||
function urlLinkTagRegex() {
|
||||
return new RegExp(/(<a.*?href\s*?=\s*?['"])((?!mailto).+?)(['"].*?>)([\s\S]*?)(<\/a>)/gim);
|
||||
}
|
||||
|
||||
// Format of input: ['a@example.com, B <b@example.com>', 'c@example.com'],
|
||||
// where each element of the array is the unparsed contents of a single
|
||||
|
@ -57,6 +40,7 @@ function parseContacts(input) {
|
|||
return contacts;
|
||||
}
|
||||
|
||||
|
||||
function parseSnippet(body) {
|
||||
const doc = new DOMParser().parseFromString(body, 'text/html')
|
||||
const skipTags = new Set(['TITLE', 'SCRIPT', 'STYLE', 'IMG']);
|
||||
|
@ -133,7 +117,7 @@ function htmlifyPlaintext(text) {
|
|||
|
||||
|
||||
function replaceMessageIdInBodyTrackingLinks(messageId, originalBody) {
|
||||
const regex = new RegExp(`(https://.+?)MESSAGE_ID`, 'g')
|
||||
const regex = new RegExp(`(${N1CloudAPI.APIRoot}.+?)MESSAGE_ID`, 'g')
|
||||
return originalBody.replace(regex, `$1${messageId}`)
|
||||
}
|
||||
|
||||
|
@ -142,7 +126,7 @@ function stripTrackingLinksFromBody(originalBody) {
|
|||
let body = originalBody.replace(/<img class="n1-open"[^<]+src="([a-zA-Z0-9-_:/.]*)">/g, () => {
|
||||
return "";
|
||||
});
|
||||
body = body.replace(urlLinkTagRegex(), (match, prefix, url, suffix, content, closingTag) => {
|
||||
body = body.replace(RegExpUtils.urlLinkTagRegex(), (match, prefix, url, suffix, content, closingTag) => {
|
||||
const param = url.split("?")[1];
|
||||
if (param) {
|
||||
const link = decodeURIComponent(param.split("=")[1]);
|
||||
|
@ -166,7 +150,7 @@ function buildTrackingBodyForRecipient({baseMessage, recipient, usesOpenTracking
|
|||
});
|
||||
}
|
||||
if (usesLinkTracking) {
|
||||
customBody = customBody.replace(urlLinkTagRegex(), (match, prefix, url, suffix, content, closingTag) => {
|
||||
customBody = customBody.replace(RegExpUtils.urlLinkTagRegex(), (match, prefix, url, suffix, content, closingTag) => {
|
||||
return `${prefix}${url}&r=${encodedEmail}${suffix}${content}${closingTag}`;
|
||||
});
|
||||
}
|
||||
|
@ -322,7 +306,7 @@ async function parseFromImap(imapMessage, desiredParts, {db, accountId, folder})
|
|||
* zero pads digit dates. To make the hashes line up, we need to ensure
|
||||
* that the date string used in the ID generation is also zero-padded.
|
||||
*/
|
||||
const messageForHashing = deepClone(parsedMessage)
|
||||
const messageForHashing = Utils.deepClone(parsedMessage)
|
||||
messageForHashing.date = Message.dateString(parsedMessage.date);
|
||||
// Inversely to `buildForSend`, we leave the date header as it is so that the
|
||||
// format is consistent for the generative IDs, then convert it to a Date object
|
||||
|
@ -423,7 +407,7 @@ async function buildForSend(db, json) {
|
|||
// nodemailer buildmail function that gives us the raw message and replaces
|
||||
// the date header with this modified UTC string
|
||||
// https://github.com/nodemailer/buildmail/blob/master/lib/buildmail.js#L470
|
||||
const messageForHashing = deepClone(message)
|
||||
const messageForHashing = Utils.deepClone(message)
|
||||
messageForHashing.date = Message.dateString(date)
|
||||
message.id = Message.hash(messageForHashing)
|
||||
message.body = replaceMessageIdInBodyTrackingLinks(message.id, message.body)
|
24
packages/client-sync/src/shared/utils.js
Normal file
24
packages/client-sync/src/shared/utils.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
module.exports = {
|
||||
copyModel(Model, model, updates = {}) {
|
||||
const fields = Object.keys(model.dataValues)
|
||||
const data = {}
|
||||
for (const field of fields) {
|
||||
// We can't just copy over the values directly from `dataValues` because
|
||||
// they are the raw values, and we would ignore custom getters.
|
||||
// Rather, we access them from the model instance.
|
||||
// For example our JSON database type, is simply a string and the custom
|
||||
// getter parses it into json. We want to get the parsed json, not the
|
||||
// string
|
||||
data[field] = model[field]
|
||||
}
|
||||
return Model.build(Object.assign({}, data, updates))
|
||||
},
|
||||
|
||||
isValidId(value) {
|
||||
if (value == null) { return false; }
|
||||
if (isNaN(parseInt(value, 36))) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
}
|
|
@ -20,7 +20,5 @@ module.exports = {
|
|||
BackoffScheduler: require('./src/backoff-schedulers').BackoffScheduler,
|
||||
ExponentialBackoffScheduler: require('./src/backoff-schedulers').ExponentialBackoffScheduler,
|
||||
MetricsReporter: require('./src/metrics-reporter').default,
|
||||
MessageFactory: require('./src/message-factory'),
|
||||
SendUtils: require('./src/send-utils'),
|
||||
executeJasmine: require('./spec/jasmine/execute').default,
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"atob": "2.0.3",
|
||||
"btoa": "1.1.2",
|
||||
"imap": "github:jstejada/node-imap#fix-parse-body-list",
|
||||
"imap-provider-settings": "github:nylas/imap-provider-settings",
|
||||
"jasmine": "2.x.x",
|
||||
|
@ -20,10 +19,7 @@
|
|||
"rx-lite": "4.0.8",
|
||||
"sequelize": "3.28.0",
|
||||
"underscore": "1.8.3",
|
||||
"xoauth2": "1.2.0",
|
||||
"he": "1.1.0",
|
||||
"iconv": "2.2.1",
|
||||
"mimelib": "0.2.19"
|
||||
"xoauth2": "1.2.0"
|
||||
},
|
||||
"author": "Nylas",
|
||||
"license": "ISC"
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
const atob = require('atob')
|
||||
const crypto = require('crypto');
|
||||
|
||||
const {JSONColumn, JSONArrayColumn} = require('../database-types');
|
||||
const {SUPPORTED_PROVIDERS, credentialsForProvider} = require('../auth-helpers');
|
||||
|
||||
|
||||
const {DB_ENCRYPTION_ALGORITHM, DB_ENCRYPTION_PASSWORD} = process.env;
|
||||
|
||||
module.exports = (sequelize, Sequelize) => {
|
||||
|
@ -138,13 +136,11 @@ module.exports = (sequelize, Sequelize) => {
|
|||
secure: ssl_required,
|
||||
};
|
||||
}
|
||||
|
||||
if (this.provider === 'gmail') {
|
||||
const {xoauth2} = connectionCredentials;
|
||||
if (!xoauth2) {
|
||||
throw new Error("Missing XOAuth2 Token")
|
||||
}
|
||||
|
||||
const token = this.bearerToken(xoauth2);
|
||||
config.auth = { user: connectionSettings.smtp_username, xoauth2: token }
|
||||
} else if (SUPPORTED_PROVIDERS.has(this.provider)) {
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
const _ = require('underscore');
|
||||
|
||||
function copyModel(Model, model, updates = {}) {
|
||||
const fields = Object.keys(model.dataValues)
|
||||
const data = {}
|
||||
for (const field of fields) {
|
||||
// We can't just copy over the values directly from `dataValues` because
|
||||
// they are the raw values, and we would ignore custom getters.
|
||||
// Rather, we access them from the model instance.
|
||||
// For example our JSON database type, is simply a string and the custom
|
||||
// getter parses it into json. We want to get the parsed json, not the
|
||||
// string
|
||||
data[field] = model[field]
|
||||
}
|
||||
return Model.build(Object.assign({}, data, updates))
|
||||
}
|
||||
|
||||
function isValidId(value) {
|
||||
if (value == null) { return false; }
|
||||
if (isNaN(parseInt(value, 36))) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function deepClone(object, customizer, stackSeen = [], stackRefs = []) {
|
||||
let newObject;
|
||||
if (!_.isObject(object)) { return object; }
|
||||
if (_.isFunction(object)) { return object; }
|
||||
|
||||
if (_.isArray(object)) {
|
||||
// http://perfectionkills.com/how-ecmascript-5-still-does-not-allow-to-subclass-an-array/
|
||||
newObject = [];
|
||||
} else if (object instanceof Date) {
|
||||
// You can't clone dates by iterating through `getOwnPropertyNames`
|
||||
// of the Date object. We need to special-case Dates.
|
||||
newObject = new Date(object);
|
||||
} else {
|
||||
newObject = Object.create(Object.getPrototypeOf(object));
|
||||
}
|
||||
|
||||
// Circular reference check
|
||||
const seenIndex = stackSeen.indexOf(object);
|
||||
if (seenIndex >= 0) { return stackRefs[seenIndex]; }
|
||||
stackSeen.push(object); stackRefs.push(newObject);
|
||||
|
||||
// It's important to use getOwnPropertyNames instead of Object.keys to
|
||||
// get the non-enumerable items as well.
|
||||
for (const key of Array.from(Object.getOwnPropertyNames(object))) {
|
||||
const newVal = deepClone(object[key], customizer, stackSeen, stackRefs);
|
||||
if (_.isFunction(customizer)) {
|
||||
newObject[key] = customizer(key, newVal);
|
||||
} else {
|
||||
newObject[key] = newVal;
|
||||
}
|
||||
}
|
||||
return newObject;
|
||||
}
|
||||
|
||||
module.exports = {copyModel, isValidId, deepClone};
|
|
@ -20,7 +20,6 @@ class SendmailClient {
|
|||
async _send(msgData) {
|
||||
let error;
|
||||
let results;
|
||||
|
||||
// disable nodemailer's automatic X-Mailer header
|
||||
msgData.xMailer = false;
|
||||
for (let i = 0; i <= MAX_RETRIES; i++) {
|
||||
|
@ -74,7 +73,7 @@ class SendmailClient {
|
|||
msgData.date = message.date;
|
||||
msgData.subject = message.subject;
|
||||
msgData.html = message.body;
|
||||
msgData.messageId = message.headerMessageId || message.message_id_header;
|
||||
msgData.messageId = message.headerMessageId;
|
||||
|
||||
msgData.attachments = []
|
||||
const uploads = message.uploads || []
|
||||
|
|
Loading…
Reference in a new issue