template-sender.js
3.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
'use strict';
var shared = require('nodemailer-shared');
module.exports = templateSender;
// expose for testing
module.exports.render = render;
/**
* Create template based e-mail sender
*
* @param {Object} transport Nodemailer transport object to use for actual sending
* @param {Object} templates Either an object with template strings or an external renderer
* @param {Object} [defaults] Default fields set for every mail sent using this sender
* @return {Function} Template based sender
*/
function templateSender(transport, templates, defaults) {
templates = templates || {};
defaults = defaults || {};
// built in renderer
var defaultRenderer = function (context, callback) {
var rendered = {};
Object.keys(templates).forEach(function (key) {
rendered[key] = render(templates[key], {
escapeHtml: key === 'html'
}, context);
});
callback(null, rendered);
};
// actual renderer
var renderer = (typeof templates.render === 'function' ? templates.render.bind(templates) : defaultRenderer);
return function (fields, context, callback) {
var promise;
if (!callback && typeof Promise === 'function') {
promise = new Promise(function (resolve, reject) {
callback = shared.callbackPromise(resolve, reject);
});
}
// render data
renderer(context, function (err, rendered) {
if (err) {
return callback(err);
}
var mailData = mix(defaults, fields, rendered);
setImmediate(function () {
transport.sendMail(mailData, callback);
});
});
return promise;
};
}
/**
* Merges multiple objects into one. Assumes single level, except 'headers'
*/
function mix( /* obj1, obj2, ..., objN */ ) {
var args = Array.prototype.slice.call(arguments);
var result = {};
args.forEach(function (arg) {
Object.keys(arg || {}).forEach(function (key) {
if (key === 'headers') {
if (!result.headers) {
result.headers = {};
}
Object.keys(arg[key]).forEach(function (hKey) {
if (!(hKey in result.headers)) {
result.headers[hKey] = arg[key][hKey];
}
});
} else if (!(key in result)) {
result[key] = arg[key];
}
});
});
return result;
}
/**
* Renders a template string using provided context. Values are marked as {{key}} in the template.
*
* @param {String} str Template string
* @param {Object} options Render options. options.escapeHtml=true escapes html specific characters
* @param {Object} context Key-value pairs for the template, eg {name: 'User Name'}
* @return {String} Rendered template
*/
function render(str, options, context) {
str = (str || '').toString();
context = context || {};
options = options || {};
var re = /\{\{[ ]*([^{}\s]+)[ ]*\}\}/g;
return str.replace(re, function (match, key) {
var value;
if (context.hasOwnProperty(key)) {
value = context[key].toString();
if (options.escapeHtml) {
value = value.replace(/["'&<>]/g, function (char) {
switch (char) {
case '&':
return '&';
case '<':
return '<';
case '>':
return '>';
case '"':
return '"';
case '\'':
return ''';
default:
return char;
}
});
}
return value;
}
return match;
});
}