tutanota/src/mail/InboxRuleHandler.js

194 lines
7.6 KiB
JavaScript
Raw Normal View History

2017-08-15 13:54:22 +02:00
//@flow
import type {MoveMailData} from "../api/entities/tutanota/MoveMailData"
2017-08-15 13:54:22 +02:00
import {createMoveMailData} from "../api/entities/tutanota/MoveMailData"
import {load, serviceRequestVoid} from "../api/main/Entity"
2017-08-15 13:54:22 +02:00
import {TutanotaService} from "../api/entities/tutanota/Services"
import {InboxRuleType, MAX_NBR_MOVE_DELETE_MAIL_SERVICE} from "../api/common/TutanotaConstants"
2019-02-13 13:38:51 +01:00
import {isDomainName, isRegularExpression} from "../misc/FormatValidator"
import {getElementId, getListId, HttpMethod, isSameId} from "../api/common/EntityFunctions"
import {debounce, getMailHeaders, noOp} from "../api/common/utils/Utils"
2017-08-15 13:54:22 +02:00
import {assertMainOrNode} from "../api/Env"
import {lang} from "../misc/LanguageViewModel"
import {MailHeadersTypeRef} from "../api/entities/tutanota/MailHeaders"
import {logins} from "../api/main/LoginController"
2017-11-24 15:14:56 +01:00
import {getInboxFolder} from "./MailUtils"
import type {MailboxDetail} from "./MailModel"
import {LockedError, NotFoundError, PreconditionFailedError} from "../api/common/error/RestError"
import type {Mail} from "../api/entities/tutanota/Mail"
import type {InboxRule} from "../api/entities/tutanota/InboxRule"
import type {MailAddress} from "../api/entities/tutanota/MailAddress"
import type {SelectorItemList} from "../gui/base/DropDownSelectorN"
import {splitInChunks} from "../api/common/utils/ArrayUtils"
2017-08-15 13:54:22 +02:00
assertMainOrNode()
const moveMailDataPerFolder: MoveMailData[] = []
const DEBOUNCE_FIRST_MOVE_MAIL_REQUEST_MS = 200
let applyingRules = false // used to avoid concurrent application of rules (-> requests to locked service)
function sendMoveMailRequest(): Promise<void> {
if (moveMailDataPerFolder.length) {
const moveToTargetFolder = moveMailDataPerFolder.shift()
const mailChunks = splitInChunks(MAX_NBR_MOVE_DELETE_MAIL_SERVICE, moveToTargetFolder.mails)
return Promise.each(mailChunks, mailChunk => {
moveToTargetFolder.mails = mailChunk
return serviceRequestVoid(TutanotaService.MoveMailService, HttpMethod.POST, moveToTargetFolder)
}).catch(LockedError, e => { //LockedError should no longer be thrown!?!
console.log("moving mail failed", e, moveToTargetFolder)
}).catch(PreconditionFailedError, e => {
// move mail operation may have been locked by other process
console.log("moving mail failed", e, moveToTargetFolder)
}).finally(() => {
return sendMoveMailRequest()
})
} else {
//We are done and unlock for future requests
return Promise.resolve()
}
}
// We throttle the moveMail requests to a rate of 50ms
// Each target folder requires one request
const applyMatchingRules = debounce(DEBOUNCE_FIRST_MOVE_MAIL_REQUEST_MS, () => {
if (applyingRules) return
// We lock to avoid concurrent requests
applyingRules = true
sendMoveMailRequest().finally(() => {
applyingRules = false
})
})
2017-08-15 13:54:22 +02:00
export function getInboxRuleTypeNameMapping(): SelectorItemList<string> {
2017-08-15 13:54:22 +02:00
return [
{value: InboxRuleType.FROM_EQUALS, name: lang.get("inboxRuleSenderEquals_action")},
{value: InboxRuleType.RECIPIENT_TO_EQUALS, name: lang.get("inboxRuleToRecipientEquals_action")},
{value: InboxRuleType.RECIPIENT_CC_EQUALS, name: lang.get("inboxRuleCCRecipientEquals_action")},
{value: InboxRuleType.RECIPIENT_BCC_EQUALS, name: lang.get("inboxRuleBCCRecipientEquals_action")},
{value: InboxRuleType.SUBJECT_CONTAINS, name: lang.get("inboxRuleSubjectContains_action")},
{value: InboxRuleType.MAIL_HEADER_CONTAINS, name: lang.get("inboxRuleMailHeaderContains_action")}
]
}
export function getInboxRuleTypeName(type: string): string {
2018-07-26 14:25:29 +02:00
let typeNameMapping = getInboxRuleTypeNameMapping().find(t => t.value === type)
2017-12-21 12:12:05 +01:00
return typeNameMapping != null ? typeNameMapping.name : ""
2017-08-15 13:54:22 +02:00
}
/**
* Checks the mail for an existing inbox rule and moves the mail to the target folder of the rule.
* @returns true if a rule matches otherwise false
*/
export function findAndApplyMatchingRule(mailboxDetail: MailboxDetail, mail: Mail): Promise<?IdTuple> {
if (mail._errors || !mail.unread || !isInboxList(mailboxDetail, getListId(mail))
2018-07-18 17:22:54 +02:00
|| !logins.getUserController().isPremiumAccount()) {
return Promise.resolve(null)
2017-08-15 13:54:22 +02:00
}
return _findMatchingRule(mail).then(inboxRule => {
if (inboxRule) {
let targetFolder = mailboxDetail.folders.filter(folder => folder !== getInboxFolder(mailboxDetail.folders))
.find(folder => isSameId(folder._id, inboxRule.targetFolder))
2017-08-15 13:54:22 +02:00
if (targetFolder) {
let moveMailData = moveMailDataPerFolder.find(folderMoveMailData => isSameId(folderMoveMailData.targetFolder, inboxRule.targetFolder))
if (moveMailData) {
moveMailData.mails.push(mail._id)
} else {
moveMailData = createMoveMailData()
moveMailData.targetFolder = inboxRule.targetFolder
moveMailData.mails.push(mail._id)
moveMailDataPerFolder.push(moveMailData)
}
applyMatchingRules()
return [targetFolder.mails, getElementId(mail)]
2017-08-15 13:54:22 +02:00
} else {
return null
2017-08-15 13:54:22 +02:00
}
} else {
return null
2017-08-15 13:54:22 +02:00
}
})
}
/**
* Finds the first matching inbox rule for the mail and returns it.
* export only for testing
*/
export function _findMatchingRule(mail: Mail): Promise<?InboxRule> {
return Promise.reduce(logins.getUserController().props.inboxRules, (resultInboxRule, inboxRule) => {
if (resultInboxRule) {
//console.log("rule matches", resultInboxRule)
return resultInboxRule
}
//console.log("find matching rule", inboxRule.value)
let ruleType = inboxRule.type;
2018-07-18 17:22:54 +02:00
if (ruleType === InboxRuleType.FROM_EQUALS) {
2017-08-15 13:54:22 +02:00
return _checkEmailAddresses([mail.sender], inboxRule)
2018-07-18 17:22:54 +02:00
} else if (ruleType === InboxRuleType.RECIPIENT_TO_EQUALS) {
2017-08-15 13:54:22 +02:00
return _checkEmailAddresses(mail.toRecipients, inboxRule)
2018-07-18 17:22:54 +02:00
} else if (ruleType === InboxRuleType.RECIPIENT_CC_EQUALS) {
2017-08-15 13:54:22 +02:00
return _checkEmailAddresses(mail.ccRecipients, inboxRule)
2018-07-18 17:22:54 +02:00
} else if (ruleType === InboxRuleType.RECIPIENT_BCC_EQUALS) {
2017-08-15 13:54:22 +02:00
return _checkEmailAddresses(mail.bccRecipients, inboxRule)
2018-07-18 17:22:54 +02:00
} else if (ruleType === InboxRuleType.SUBJECT_CONTAINS) {
2017-08-15 13:54:22 +02:00
return _checkContainsRule(mail.subject, inboxRule)
2018-07-18 17:22:54 +02:00
} else if (ruleType === InboxRuleType.MAIL_HEADER_CONTAINS) {
2017-08-15 13:54:22 +02:00
if (mail.headers) {
return load(MailHeadersTypeRef, mail.headers)
.then(mailHeaders => {
return _checkContainsRule(getMailHeaders(mailHeaders), inboxRule)
})
.catch(NotFoundError, noOp)
2017-08-15 13:54:22 +02:00
} else {
return null
}
} else {
return null
}
}, null)
}
function _checkContainsRule(value: string, inboxRule: InboxRule): ?InboxRule {
if (isRegularExpression(inboxRule.value) && _matchesRegularExpression(value, inboxRule)) {
return inboxRule
} else if (value.indexOf(inboxRule.value) >= 0) {
return inboxRule
} else {
return null
}
}
2018-07-18 17:22:54 +02:00
2017-08-15 13:54:22 +02:00
/** export for test. */
export function _matchesRegularExpression(value: string, inboxRule: InboxRule): boolean {
if (isRegularExpression(inboxRule.value)) {
let flags = inboxRule.value.replace(/.*\/([gimsuy]*)$/, '$1');
let pattern = inboxRule.value.replace(new RegExp('^/(.*?)/' + flags + '$'), '$1');
let regExp = new RegExp(pattern, flags);
2017-08-15 13:54:22 +02:00
return regExp.test(value)
}
return false
}
function _checkEmailAddresses(mailAddresses: MailAddress[], inboxRule: InboxRule): ?InboxRule {
let mailAddress = mailAddresses.find(mailAddress => {
let cleanMailAddress = mailAddress.address.toLowerCase().trim();
if (isRegularExpression(inboxRule.value)) {
return _matchesRegularExpression(cleanMailAddress, inboxRule)
} else if (isDomainName(inboxRule.value)) {
let domain = cleanMailAddress.split("@")[1];
2018-07-18 17:22:54 +02:00
return domain === inboxRule.value
2017-08-15 13:54:22 +02:00
} else {
2018-07-18 17:22:54 +02:00
return cleanMailAddress === inboxRule.value
2017-08-15 13:54:22 +02:00
}
})
if (mailAddress) {
return inboxRule
} else {
return null
}
}
export function isInboxList(mailboxDetail: MailboxDetail, listId: Id): boolean {
2017-11-24 15:14:56 +01:00
return isSameId(listId, getInboxFolder(mailboxDetail.folders).mails)
2017-08-15 13:54:22 +02:00
}