mailtrain/server/lib/tools.js

194 lines
5.4 KiB
JavaScript
Raw Normal View History

2016-04-04 12:36:30 +00:00
'use strict';
2018-04-29 16:13:40 +00:00
const util = require('util');
const isemail = require('isemail');
const path = require('path');
const {getPublicUrl} = require('./urls');
2016-04-04 12:36:30 +00:00
2018-04-29 16:13:40 +00:00
const bluebird = require('bluebird');
2016-04-04 12:36:30 +00:00
2018-04-29 16:13:40 +00:00
const hasher = require('node-object-hash')();
2018-11-10 01:05:26 +00:00
2018-04-29 16:13:40 +00:00
const mjml = require('mjml');
2018-11-10 01:05:26 +00:00
const mjml2html = mjml.default;
2018-04-29 16:13:40 +00:00
const hbs = require('hbs');
const juice = require('juice');
const he = require('he');
2018-04-29 16:13:40 +00:00
const fs = require('fs-extra');
2018-04-29 16:13:40 +00:00
const { JSDOM } = require('jsdom');
const { tUI, tLog } = require('./translate');
2018-04-29 16:13:40 +00:00
2018-04-29 16:13:40 +00:00
const templates = new Map();
2016-04-04 12:36:30 +00:00
2018-04-29 16:13:40 +00:00
async function getTemplate(template) {
if (!template) {
return false;
}
2016-04-04 12:36:30 +00:00
2018-04-29 16:13:40 +00:00
const key = (typeof template === 'object') ? hasher.hash(template) : template;
2016-04-04 12:36:30 +00:00
2018-04-29 16:13:40 +00:00
if (templates.has(key)) {
return templates.get(key);
}
let source;
if (typeof template === 'object') {
source = await mergeTemplateIntoLayout(template.template, template.layout);
} else {
source = await fs.readFile(path.join(__dirname, '..', 'views', template), 'utf-8');
2016-04-04 12:36:30 +00:00
}
2018-04-29 16:13:40 +00:00
if (template.type === 'mjml') {
2018-11-10 01:05:26 +00:00
const compiled = mjml2html(source);
2018-04-29 16:13:40 +00:00
if (compiled.errors.length) {
throw new Error(compiled.errors[0].message || compiled.errors[0]);
2016-04-04 12:36:30 +00:00
}
2018-04-29 16:13:40 +00:00
source = compiled.html;
}
const renderer = hbs.handlebars.compile(source);
2018-04-29 16:13:40 +00:00
templates.set(key, renderer);
return renderer;
2016-04-04 12:36:30 +00:00
}
2018-04-29 16:13:40 +00:00
async function mergeTemplateIntoLayout(template, layout) {
layout = layout || '{{{body}}}';
async function readFile(relPath) {
return await fs.readFile(path.join(__dirname, '..', 'views', relPath), 'utf-8');
2016-04-04 12:36:30 +00:00
}
2018-04-29 16:13:40 +00:00
// Please dont end your custom messages with .hbs ...
if (layout.endsWith('.hbs')) {
layout = await readFile(layout);
}
2017-04-25 22:49:31 +00:00
2018-04-29 16:13:40 +00:00
if (template.endsWith('.hbs')) {
template = await readFile(template);
2017-04-25 22:49:31 +00:00
}
2018-04-29 16:13:40 +00:00
const source = layout.replace(/\{\{\{body\}\}\}/g, template);
return source;
2016-04-04 12:36:30 +00:00
}
2018-09-01 19:29:10 +00:00
async function validateEmail(address) {
2018-04-29 16:13:40 +00:00
const result = await new Promise(resolve => {
const result = isemail.validate(address, {
checkDNS: true,
errorLevel: 1
}, resolve);
2016-04-04 12:36:30 +00:00
});
2018-04-29 16:13:40 +00:00
return result;
2016-04-26 17:29:57 +00:00
}
function validateEmailGetMessage(result, address, language) {
let t;
if (language) {
t = (key, args) => tUI(key, language, args);
} else {
t = (key, args) => tLog(key, args);
}
2018-04-29 16:13:40 +00:00
if (result !== 0) {
switch (result) {
case 5:
return t('addressCheck.mxNotFound', {email: address});
2018-04-29 16:13:40 +00:00
case 6:
return t('addressCheck.domainNotFound', {email: address});
2018-04-29 16:13:40 +00:00
case 12:
return t('addressCheck.domainRequired', {email: address});
default:
return t('invalidEmailGeneric', {email: address});
2016-04-04 12:36:30 +00:00
}
2018-04-29 16:13:40 +00:00
}
2016-04-04 12:36:30 +00:00
}
2016-05-03 16:21:01 +00:00
function formatMessage(campaign, list, subscription, mergeTags, message, isHTML) {
2018-09-27 10:34:54 +00:00
const links = getMessageLinks(campaign, list, subscription);
2018-09-27 10:34:54 +00:00
const getValue = key => {
key = (key || '').toString().toUpperCase().trim();
if (links.hasOwnProperty(key)) {
return links[key];
}
if (mergeTags.hasOwnProperty(key)) {
2018-09-27 10:34:54 +00:00
const value = (mergeTags[key] || '').toString();
const containsHTML = /<[a-z][\s\S]*>/.test(value);
return isHTML ? he.encode((containsHTML ? value : value.replace(/(?:\r\n|\r|\n)/g, '<br/>')), {
useNamedReferences: true,
allowUnsafeSymbols: true
}) : (containsHTML ? htmlToText.fromString(value) : value);
}
return false;
};
return message.replace(/\[([a-z0-9_]+)(?:\/([^\]]+))?\]/ig, (match, identifier, fallback) => {
let value = getValue(identifier);
if (value === false) {
return match;
}
value = (value || fallback || '').trim();
return value;
});
}
async function prepareHtml(html) {
2016-05-03 16:21:01 +00:00
if (!(html || '').toString().trim()) {
2018-04-29 16:13:40 +00:00
return false;
2016-05-03 16:21:01 +00:00
}
2018-04-29 16:13:40 +00:00
const { window } = new JSDOM(html);
2018-04-29 16:13:40 +00:00
const head = window.document.querySelector('head');
2018-04-29 16:13:40 +00:00
let hasCharsetTag = false;
const metaTags = window.document.querySelectorAll('meta');
2018-04-29 16:13:40 +00:00
if (metaTags) {
for (let i = 0; i < metaTags.length; i++) {
if (metaTags[i].hasAttribute('charset')) {
metaTags[i].setAttribute('charset', 'utf-8');
hasCharsetTag = true;
break;
2016-05-03 16:21:01 +00:00
}
}
2018-04-29 16:13:40 +00:00
}
if (!hasCharsetTag) {
const charsetTag = window.document.createElement('meta');
2018-04-29 16:13:40 +00:00
charsetTag.setAttribute('charset', 'utf-8');
head.appendChild(charsetTag);
}
const preparedHtml = '<!doctype html><html>' + window.document.documentElement.innerHTML + '</html>';
2016-05-03 16:21:01 +00:00
2018-04-29 16:13:40 +00:00
return juice(preparedHtml);
2016-05-03 16:21:01 +00:00
}
2017-03-19 12:36:57 +00:00
function getMessageLinks(campaign, list, subscription) {
return {
LINK_UNSUBSCRIBE: getPublicUrl('/subscription/' + list.cid + '/unsubscribe/' + subscription.cid + '?c=' + campaign.cid),
LINK_PREFERENCES: getPublicUrl('/subscription/' + list.cid + '/manage/' + subscription.cid),
LINK_BROWSER: getPublicUrl('/archive/' + campaign.cid + '/' + list.cid + '/' + subscription.cid),
CAMPAIGN_ID: campaign.cid,
LIST_ID: list.cid,
SUBSCRIPTION_ID: subscription.cid
};
}
2017-03-19 12:36:57 +00:00
2018-04-29 16:13:40 +00:00
module.exports = {
validateEmail,
validateEmailGetMessage,
mergeTemplateIntoLayout,
getTemplate,
prepareHtml,
2018-09-27 10:34:54 +00:00
getMessageLinks,
formatMessage
2018-04-29 16:13:40 +00:00
};
2017-03-19 12:36:57 +00:00