Revert "[feat] Add support for send later"

This reverts commit 683a550d49.
This commit is contained in:
Karim Hamidou 2017-03-07 16:18:25 -08:00
parent 683a550d49
commit 2f67d8ac8b
14 changed files with 47 additions and 193 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
const {MessageFactory} = require('isomorphic-core')
const MessageFactory = require('../../shared/message-factory')
const {SyncbackSMTPTask} = require('../syncback-tasks/syncback-task')
/**

View file

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

View file

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

View 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
},
}

View file

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

View file

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

View file

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

View file

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

View file

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