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