@futdevpro/nts-dynamo 1.9.15 → 1.9.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.copilot/patterns.json +7 -7
- package/.github/workflows/main.yml +206 -0
- package/HOWTO.md +15 -15
- package/README.md +140 -140
- package/build/_models/control-models/endpoint-params.control-model.d.ts.map +1 -1
- package/build/_models/control-models/endpoint-params.control-model.js +2 -0
- package/build/_models/control-models/endpoint-params.control-model.js.map +1 -1
- package/build/_models/control-models/socket-event.control-model.js +1 -1
- package/build/_services/core/global.service.d.ts.map +1 -1
- package/build/_services/core/global.service.js +1 -0
- package/build/_services/core/global.service.js.map +1 -1
- package/build/_services/route/routing-module.service.d.ts +1 -0
- package/build/_services/route/routing-module.service.d.ts.map +1 -1
- package/build/_services/route/routing-module.service.js +17 -23
- package/build/_services/route/routing-module.service.js.map +1 -1
- package/build/_services/server/app.server.d.ts.map +1 -1
- package/build/_services/server/app.server.js +4 -1
- package/build/_services/server/app.server.js.map +1 -1
- package/build/_services/socket/socket-client.service.d.ts.map +1 -1
- package/build/_services/socket/socket-client.service.js +1 -0
- package/build/_services/socket/socket-client.service.js.map +1 -1
- package/nodemon.json +17 -15
- package/package.json +5 -5
- package/src/_constants/global-settings.const.ts +27 -27
- package/src/_constants/index.ts +2 -2
- package/src/_constants/mocks/app-extended-server.mock.ts +198 -198
- package/src/_constants/mocks/app-params.mock.ts +9 -9
- package/src/_constants/mocks/app-server.mock.ts +185 -185
- package/src/_constants/mocks/auth-service.mock.ts +28 -28
- package/src/_constants/mocks/controller.mock.ts +16 -16
- package/src/_constants/mocks/data-model.mock.ts +83 -83
- package/src/_constants/mocks/email-service-collection.mock.ts +13 -13
- package/src/_constants/mocks/email-service.mock.ts +19 -19
- package/src/_constants/mocks/email-template.mock.html +14 -14
- package/src/_constants/mocks/endpoint.mock.ts +90 -90
- package/src/_constants/mocks/socket-client.mock.ts +43 -43
- package/src/_constants/mocks/socket-server.mock.ts +43 -43
- package/src/_enums/data-model-type.enum.ts +14 -14
- package/src/_enums/data-service-function.enum.ts +15 -15
- package/src/_enums/http/http-call-type.enum.ts +12 -12
- package/src/_enums/http/http-response-type.enum.ts +7 -7
- package/src/_enums/http/socket-event-type.enum.ts +18 -18
- package/src/_enums/index.ts +13 -13
- package/src/_enums/predefined-data-types.enum.ts +27 -27
- package/src/_enums/route-security.enum.ts +12 -12
- package/src/_enums/socket-security.enum.ts +11 -11
- package/src/_models/control-models/api-call-params.control-model.ts +126 -126
- package/src/_models/control-models/app-ext-system-controls.control-model.ts +9 -9
- package/src/_models/control-models/app-params.control-model.ts +45 -45
- package/src/_models/control-models/app-system-controls.control-model.ts +9 -9
- package/src/_models/control-models/endpoint-params.control-model.ts +309 -307
- package/src/_models/control-models/http-settings.control-model.ts +29 -29
- package/src/_models/control-models/index.ts +13 -13
- package/src/_models/control-models/socket-client-service-params.control-model.ts +28 -28
- package/src/_models/control-models/socket-event.control-model.ts +150 -150
- package/src/_models/control-models/socket-presence.control-model.ts +207 -207
- package/src/_models/control-models/socket-server-service-params.control-model.ts +20 -20
- package/src/_models/control-models/system-control.control-model.ts +12 -12
- package/src/_models/index.ts +9 -9
- package/src/_models/interfaces/certification-settings.interface.ts +7 -7
- package/src/_models/interfaces/global-service-settings.interface.ts +45 -45
- package/src/_models/interfaces/global-settings.interface.ts +83 -83
- package/src/_models/interfaces/index.ts +7 -7
- package/src/_models/interfaces/routing-module-settings.interface.ts +20 -20
- package/src/_models/types/db-filter.type.ts +108 -108
- package/src/_models/types/db-update.type.ts +100 -100
- package/src/_models/types/index.ts +5 -5
- package/src/_modules/api-service.index.ts +12 -12
- package/src/_modules/app-extended.index.ts +28 -28
- package/src/_modules/app.index.ts +24 -24
- package/src/_modules/auth.index.ts +7 -7
- package/src/_modules/constants.index.ts +2 -2
- package/src/_modules/controller.index.ts +10 -10
- package/src/_modules/custom-data/custom-data.controller.ts +69 -69
- package/src/_modules/custom-data/custom-data.data-service.ts +20 -20
- package/src/_modules/custom-data/get-custom-data-routing-module.util.ts +23 -23
- package/src/_modules/custom-data/index.ts +6 -6
- package/src/_modules/custom-data-module.index.ts +2 -2
- package/src/_modules/data-service.index.ts +9 -9
- package/src/_modules/email.index.ts +8 -8
- package/src/_modules/enums.index.ts +2 -2
- package/src/_modules/extended.index.ts +8 -8
- package/src/_modules/models.index.ts +2 -2
- package/src/_modules/services.index.ts +2 -2
- package/src/_modules/test/get-test-routing-module.util.ts +23 -23
- package/src/_modules/test/index.ts +5 -5
- package/src/_modules/test/test.controller.ts +115 -115
- package/src/_modules/test-module.index.ts +2 -2
- package/src/_modules/usage/get-usage-routing-module.util.ts +22 -22
- package/src/_modules/usage/index.ts +7 -7
- package/src/_modules/usage/usage.controller.ts +120 -120
- package/src/_modules/usage/usage.data-service.ts +172 -172
- package/src/_modules/usage-module.index.ts +2 -2
- package/src/_services/base/data.service.ts +921 -921
- package/src/_services/base/db.service.spec.ts +32 -32
- package/src/_services/base/db.service.ts +1063 -1063
- package/src/_services/base/singleton.service.ts +21 -21
- package/src/_services/core/api.service.ts +453 -453
- package/src/_services/core/auth.service.ts +172 -172
- package/src/_services/core/email.service.ts +678 -678
- package/src/_services/core/global.service.ts +270 -269
- package/src/_services/core/service-collection.service.ts +5 -5
- package/src/_services/index.ts +23 -23
- package/src/_services/route/controller.service.ts +129 -129
- package/src/_services/route/routing-module.service.ts +293 -273
- package/src/_services/server/app-extended.server.spec.ts +76 -76
- package/src/_services/server/app-extended.server.ts +520 -520
- package/src/_services/server/app.server.spec.ts +67 -67
- package/src/_services/server/app.server.ts +1181 -1179
- package/src/_services/shared.service.spec.ts +19 -19
- package/src/_services/shared.static-service.ts +73 -73
- package/src/_services/socket/socket-client.service.ts +236 -235
- package/src/_services/socket/socket-server.service.spec.ts +11 -11
- package/src/_services/socket/socket-server.service.ts +761 -761
- package/src/index.ts +18 -18
- package/tsconfig.json +41 -41
- package/build/tsconfig.tsbuildinfo +0 -1
|
@@ -1,678 +1,678 @@
|
|
|
1
|
-
|
|
2
|
-
import * as FileSystem from 'fs';
|
|
3
|
-
import * as Path from 'path';
|
|
4
|
-
import * as NodeMailer from 'nodemailer';
|
|
5
|
-
|
|
6
|
-
import { Options as MailOptions, Attachment } from 'nodemailer/lib/mailer';
|
|
7
|
-
|
|
8
|
-
import { DynamoFM_AnyError, DynamoFM_Array, DynamoFM_Error, DynamoFM_Error_Settings, DynamoFM_Log } from '@futdevpro/fsm-dynamo';
|
|
9
|
-
|
|
10
|
-
export interface DynamoNTS_EmailService_Settings {
|
|
11
|
-
host: string,
|
|
12
|
-
port: number,
|
|
13
|
-
email: string,
|
|
14
|
-
pass: string,
|
|
15
|
-
senderName: string,
|
|
16
|
-
rootPath: string,
|
|
17
|
-
templateComponents?: DynamoNTS_EmailTemplateComponent[],
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export class DynamoNTS_EmailTemplateComponent {
|
|
21
|
-
name: string;
|
|
22
|
-
selector: string;
|
|
23
|
-
|
|
24
|
-
templatePath?: string;
|
|
25
|
-
template?: string;
|
|
26
|
-
|
|
27
|
-
stylePath?: string;
|
|
28
|
-
styles?: string;
|
|
29
|
-
|
|
30
|
-
headerLinks?: string = '';
|
|
31
|
-
|
|
32
|
-
properties?: string[] = [];
|
|
33
|
-
subComponentSelectors?: string[] = [];
|
|
34
|
-
|
|
35
|
-
constructor(
|
|
36
|
-
set?: DynamoNTS_EmailTemplateComponent
|
|
37
|
-
) {
|
|
38
|
-
if (set) {
|
|
39
|
-
Object.assign(this, set);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface DynamoNTS_SendEmail_Settings<T = { [propertyKey: string]: string; }> {
|
|
45
|
-
to: string;
|
|
46
|
-
subject: string;
|
|
47
|
-
/** direct email content, if provided, this will be used */
|
|
48
|
-
content?: string;
|
|
49
|
-
|
|
50
|
-
templateComponentName?: string;
|
|
51
|
-
templateProperties?: T;
|
|
52
|
-
|
|
53
|
-
attachments?: Attachment[];
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
*
|
|
58
|
-
*/
|
|
59
|
-
export class DynamoNTS_EmailService {
|
|
60
|
-
serviceName: string;
|
|
61
|
-
|
|
62
|
-
private nmTransporter: NodeMailer.Transporter;
|
|
63
|
-
private senderName: string;
|
|
64
|
-
private senderNEmail: string;
|
|
65
|
-
|
|
66
|
-
private components: DynamoNTS_EmailTemplateComponent[] = [];
|
|
67
|
-
private componentsBySelector: {
|
|
68
|
-
[selector: string]: DynamoNTS_EmailTemplateComponent
|
|
69
|
-
} = {};
|
|
70
|
-
private componentsByName: {
|
|
71
|
-
[componentName: string]: DynamoNTS_EmailTemplateComponent
|
|
72
|
-
} = {};
|
|
73
|
-
|
|
74
|
-
private readonly styleLimitWarning: number = 16;
|
|
75
|
-
|
|
76
|
-
defaultErrorUserMsg =
|
|
77
|
-
`We encountered an uncought Email Service Error, ` +
|
|
78
|
-
`\nplease contact the responsible development team.`;
|
|
79
|
-
|
|
80
|
-
constructor (
|
|
81
|
-
set: DynamoNTS_EmailService_Settings
|
|
82
|
-
) {
|
|
83
|
-
this.serviceName = this.constructor?.name;
|
|
84
|
-
this.senderName = set.senderName;
|
|
85
|
-
this.senderNEmail = `${set.senderName} <${set.email}>`;
|
|
86
|
-
this.nmTransporter = NodeMailer.createTransport({
|
|
87
|
-
host: set.host,
|
|
88
|
-
port: set.port,
|
|
89
|
-
auth: {
|
|
90
|
-
user: set.email,
|
|
91
|
-
pass: set.pass,
|
|
92
|
-
},
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
this.components = set.templateComponents ?? [];
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
async asyncPostConstruct(): Promise<void> {
|
|
99
|
-
try {
|
|
100
|
-
if (this.components) {
|
|
101
|
-
await DynamoFM_Array.asyncForEach(
|
|
102
|
-
this.components,
|
|
103
|
-
async (component: DynamoNTS_EmailTemplateComponent): Promise<void> => {
|
|
104
|
-
await this.loadComponent(component);
|
|
105
|
-
}
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
this.connectComponents();
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
DynamoFM_Log.success(`\nEmailService construction (${this.senderName}) Finished!`);
|
|
112
|
-
} catch (error) {
|
|
113
|
-
throw new DynamoFM_Error({
|
|
114
|
-
...this._getDefaultErrorSettings('asyncPostConstruct', error, 'SYSTEM'),
|
|
115
|
-
|
|
116
|
-
errorCode: 'NTS-ES0-APC0',
|
|
117
|
-
message:
|
|
118
|
-
`\nDynamoBEEmailService ERROR, ` +
|
|
119
|
-
`\nThe emailService construction failed for ${this.serviceName}.`,
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
*
|
|
126
|
-
* @param set
|
|
127
|
-
*/
|
|
128
|
-
public async sendEmail<T>(
|
|
129
|
-
set: DynamoNTS_SendEmail_Settings<T>,
|
|
130
|
-
issuer: string
|
|
131
|
-
): Promise<void> {
|
|
132
|
-
try {
|
|
133
|
-
let content: string;
|
|
134
|
-
|
|
135
|
-
if (set.content) {
|
|
136
|
-
content = set.content;
|
|
137
|
-
|
|
138
|
-
} else {
|
|
139
|
-
if (!set.templateComponentName) {
|
|
140
|
-
throw new DynamoFM_Error({
|
|
141
|
-
...this._getDefaultErrorSettings(
|
|
142
|
-
'sendEmail',
|
|
143
|
-
new Error(
|
|
144
|
-
`No email template component is given!`
|
|
145
|
-
),
|
|
146
|
-
issuer
|
|
147
|
-
),
|
|
148
|
-
|
|
149
|
-
errorCode: 'NTS-ES0-SE1',
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if (!this.componentsByName[set.templateComponentName]) {
|
|
154
|
-
throw new DynamoFM_Error({
|
|
155
|
-
...this._getDefaultErrorSettings(
|
|
156
|
-
'sendEmail',
|
|
157
|
-
new Error(
|
|
158
|
-
`No email template component found with this name! (${set.templateComponentName})`
|
|
159
|
-
),
|
|
160
|
-
issuer
|
|
161
|
-
),
|
|
162
|
-
|
|
163
|
-
errorCode: 'NTS-ES0-SE2',
|
|
164
|
-
additionalContent: {
|
|
165
|
-
availableComponenets: Object.keys(this.componentsByName),
|
|
166
|
-
},
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
content = this.compileTemplateComponent(
|
|
171
|
-
set.templateComponentName,
|
|
172
|
-
set.templateProperties,
|
|
173
|
-
issuer
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const mailOptions: MailOptions = {
|
|
178
|
-
from: this.senderNEmail,
|
|
179
|
-
to: set.to,
|
|
180
|
-
subject: set.subject,
|
|
181
|
-
html: content,
|
|
182
|
-
attachments: set.attachments,
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
await new Promise<void>((resolve, reject): void => {
|
|
186
|
-
this.nmTransporter.sendMail(mailOptions, (error): void => {
|
|
187
|
-
if (error) {
|
|
188
|
-
reject(error);
|
|
189
|
-
} else {
|
|
190
|
-
resolve();
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
});
|
|
194
|
-
} catch (error) {
|
|
195
|
-
if ((error as Error).message?.includes?.('all recipients were rejected')) {
|
|
196
|
-
throw new DynamoFM_Error({
|
|
197
|
-
...this._getDefaultErrorSettings('sendEmail', error, issuer),
|
|
198
|
-
|
|
199
|
-
addECToUserMsg: false,
|
|
200
|
-
errorCode: 'NTS-ES0-SE4',
|
|
201
|
-
userMessage: `Can't send mail to ${set.to}!`,
|
|
202
|
-
message: `sendEmail failed to ${set.to}`,
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
} else {
|
|
206
|
-
throw new DynamoFM_Error({
|
|
207
|
-
...this._getDefaultErrorSettings('sendEmail', error, issuer),
|
|
208
|
-
|
|
209
|
-
errorCode: 'NTS-ES0-SE0',
|
|
210
|
-
message: `sendEmail failed to ${set.to}`,
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
private compileTemplateComponent<T>(
|
|
217
|
-
componentName: string,
|
|
218
|
-
componentProperties:T,
|
|
219
|
-
issuer: string
|
|
220
|
-
): string {
|
|
221
|
-
try {
|
|
222
|
-
if (!this.componentsByName[componentName]) {
|
|
223
|
-
throw new DynamoFM_Error({
|
|
224
|
-
...this._getDefaultErrorSettings(
|
|
225
|
-
'setupComponent',
|
|
226
|
-
new Error(`No email component found with this name! (${componentName})`),
|
|
227
|
-
issuer
|
|
228
|
-
),
|
|
229
|
-
|
|
230
|
-
errorCode: 'NTS-ES0-SC1',
|
|
231
|
-
additionalContent: {
|
|
232
|
-
availableComponenets: Object.keys(this.componentsByName),
|
|
233
|
-
},
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
const component: DynamoNTS_EmailTemplateComponent = this.componentsByName[componentName];
|
|
238
|
-
let template: string = this.compileHTMLMainFrame(component);
|
|
239
|
-
|
|
240
|
-
template = template.replace('<nts-content>', '');
|
|
241
|
-
template = this.compileComponentProperties<T>(
|
|
242
|
-
component,
|
|
243
|
-
componentProperties,
|
|
244
|
-
template,
|
|
245
|
-
issuer
|
|
246
|
-
);
|
|
247
|
-
|
|
248
|
-
component.subComponentSelectors.forEach((subComponentSelector: string): void => {
|
|
249
|
-
while (template.includes(`<nts-${subComponentSelector}`)) {
|
|
250
|
-
template = this.compileSubComponent<T>(
|
|
251
|
-
subComponentSelector, componentProperties, template, issuer
|
|
252
|
-
);
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
template = this.templateTrim(template);
|
|
257
|
-
|
|
258
|
-
return template;
|
|
259
|
-
} catch (error) {
|
|
260
|
-
throw new DynamoFM_Error({
|
|
261
|
-
...this._getDefaultErrorSettings('setupComponent', error, issuer),
|
|
262
|
-
|
|
263
|
-
errorCode: 'NTS-ES0-SC0',
|
|
264
|
-
message: `setupComponent failed! (${componentName})`,
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
private compileHTMLMainFrame(component: DynamoNTS_EmailTemplateComponent): string {
|
|
270
|
-
const allLinks: string[] = [];
|
|
271
|
-
const allStyles: string[] = [];
|
|
272
|
-
|
|
273
|
-
if (component.headerLinks) {
|
|
274
|
-
allLinks.push(component.headerLinks);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
if (component.styles) {
|
|
278
|
-
allStyles.push(component.styles);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
component.subComponentSelectors.forEach((subComponentSelector: string): void => {
|
|
282
|
-
const subComponent: DynamoNTS_EmailTemplateComponent =
|
|
283
|
-
this.componentsBySelector[subComponentSelector];
|
|
284
|
-
|
|
285
|
-
if (subComponent.headerLinks) {
|
|
286
|
-
allLinks.push(subComponent.headerLinks.trim());
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
if (subComponent.styles) {
|
|
290
|
-
allStyles.push(subComponent.styles.trim());
|
|
291
|
-
}
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
const styles = this.styleTrim(allStyles.join(''));
|
|
295
|
-
const styleSize = this.getStringKBSize(styles);
|
|
296
|
-
|
|
297
|
-
if (this.styleLimitWarning < styleSize) {
|
|
298
|
-
DynamoFM_Log.warn(
|
|
299
|
-
`\nDynamoNTS EmailService WARNING (${this.serviceName}), ` +
|
|
300
|
-
`\nEmail template styles are too big! (${styleSize}KB)` +
|
|
301
|
-
`\ncomponent: ${component.name} (${component.selector})` +
|
|
302
|
-
`\nSome email clients may not support this!` +
|
|
303
|
-
`\nThe limit is ${this.styleLimitWarning}KB!`
|
|
304
|
-
);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
return `\n<!DOCTYPE html>` +
|
|
308
|
-
`\n<html>` +
|
|
309
|
-
`\n <head>` +
|
|
310
|
-
`\n ${allLinks.join('')}` +
|
|
311
|
-
`\n` +
|
|
312
|
-
`\n <style>` +
|
|
313
|
-
`\n ${styles}` +
|
|
314
|
-
`\n </style>` +
|
|
315
|
-
`\n </head>` +
|
|
316
|
-
`\n <body>` +
|
|
317
|
-
`\n ${component.template}` +
|
|
318
|
-
`\n </body>` +
|
|
319
|
-
`\n</html> `;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/** trims all rows and remove everything between /*...*\/ */
|
|
323
|
-
private styleTrim(style: string): string {
|
|
324
|
-
let result: string = style.split('\n').map((row: string): string => row.trim()).join('');
|
|
325
|
-
|
|
326
|
-
while (result.includes('/*')) {
|
|
327
|
-
const start = result.indexOf('/*');
|
|
328
|
-
const end = result.indexOf('*/', start) + 2;
|
|
329
|
-
|
|
330
|
-
result = result.substring(0, start) + result.substring(end);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
return result;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/** removes HTML comments */
|
|
337
|
-
private templateTrim(template: string): string {
|
|
338
|
-
let result: string = template;
|
|
339
|
-
|
|
340
|
-
while (result.includes('<!--')) {
|
|
341
|
-
const start = result.indexOf('<!--');
|
|
342
|
-
const end = result.indexOf('-->', start) + 3;
|
|
343
|
-
|
|
344
|
-
result = result.substring(0, start) + result.substring(end);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
return result;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
private getStringKBSize(str: string): number {
|
|
352
|
-
const b: number = str.length * 2;
|
|
353
|
-
|
|
354
|
-
return b / 1024;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
private compileComponentProperties<T>(
|
|
358
|
-
component: DynamoNTS_EmailTemplateComponent,
|
|
359
|
-
componentProperties: T,
|
|
360
|
-
template: string,
|
|
361
|
-
issuer: string
|
|
362
|
-
): string {
|
|
363
|
-
component.properties.forEach((propertyKey: string): void => {
|
|
364
|
-
if (!componentProperties[propertyKey]) {
|
|
365
|
-
throw new DynamoFM_Error({
|
|
366
|
-
...this._getDefaultErrorSettings(
|
|
367
|
-
'setupComponent',
|
|
368
|
-
new Error(
|
|
369
|
-
`ComponentProperty missing from input! '${propertyKey}' for ${component.name}`
|
|
370
|
-
),
|
|
371
|
-
issuer
|
|
372
|
-
),
|
|
373
|
-
|
|
374
|
-
errorCode: 'NTS-ES0-SC4',
|
|
375
|
-
additionalContent: {
|
|
376
|
-
componentProperties: Object.keys(componentProperties),
|
|
377
|
-
},
|
|
378
|
-
});
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
const propReg = new RegExp(`{{${propertyKey}}}`, 'g');
|
|
382
|
-
|
|
383
|
-
template = template.replace(propReg, componentProperties[propertyKey]);
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
return template;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
private compileSubComponent<T>(
|
|
390
|
-
subComponentSelector: string,
|
|
391
|
-
componentProperties: T,
|
|
392
|
-
template: string,
|
|
393
|
-
issuer: string
|
|
394
|
-
): string {
|
|
395
|
-
const subComponent: DynamoNTS_EmailTemplateComponent =
|
|
396
|
-
this.componentsBySelector[subComponentSelector];
|
|
397
|
-
|
|
398
|
-
if (!subComponent) {
|
|
399
|
-
throw new DynamoFM_Error({
|
|
400
|
-
...this._getDefaultErrorSettings(
|
|
401
|
-
'setupComponent',
|
|
402
|
-
new Error(`SubComponent missing from components! (${subComponentSelector})`),
|
|
403
|
-
issuer
|
|
404
|
-
),
|
|
405
|
-
|
|
406
|
-
errorCode: 'NTS-ES0-SC2',
|
|
407
|
-
additionalContent: this.componentsBySelector,
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
let subComponentTemplate: string = subComponent.template;
|
|
412
|
-
const startTag = `<nts-${subComponent.selector}`;
|
|
413
|
-
const endTag = `</nts-${subComponent.selector}>`;
|
|
414
|
-
const tagHTMLProperties: string =
|
|
415
|
-
template.split(startTag)[1].split('>')[0];
|
|
416
|
-
let componentContent: string;
|
|
417
|
-
|
|
418
|
-
if (!tagHTMLProperties) {
|
|
419
|
-
componentContent = template.split(`${startTag}>`)[1].split(endTag)[0];
|
|
420
|
-
} else {
|
|
421
|
-
componentContent = template.split(startTag)[1].split('>')[1].split(endTag)[0];
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
const contentReg = new RegExp(`<nts-content>`, 'g');
|
|
425
|
-
|
|
426
|
-
subComponentTemplate = subComponentTemplate.replace(contentReg, componentContent);
|
|
427
|
-
subComponentTemplate = this.compileComponentProperties<T>(
|
|
428
|
-
subComponent,
|
|
429
|
-
componentProperties,
|
|
430
|
-
subComponentTemplate,
|
|
431
|
-
issuer
|
|
432
|
-
);
|
|
433
|
-
|
|
434
|
-
const startTagReg = new RegExp(startTag, 'g');
|
|
435
|
-
const endTagReg = new RegExp(endTag, 'g');
|
|
436
|
-
|
|
437
|
-
template = template.replace(startTagReg, '<div');
|
|
438
|
-
template = template.replace(componentContent, subComponentTemplate);
|
|
439
|
-
template = template.replace(endTagReg, '</div>');
|
|
440
|
-
|
|
441
|
-
/* while (template.includes(`<nts-${subComponent.selector}`)) {
|
|
442
|
-
const replaceStartIndex = template.indexOf(`<nts-${subComponent.selector}`);
|
|
443
|
-
const replaceEndIndex = template.indexOf(endTag, replaceStartIndex) + endTag.length;
|
|
444
|
-
|
|
445
|
-
template =
|
|
446
|
-
template.substring(0, replaceStartIndex) +
|
|
447
|
-
subComponentCompiled +
|
|
448
|
-
template.substring(replaceEndIndex);
|
|
449
|
-
} */
|
|
450
|
-
|
|
451
|
-
return template;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
private async loadComponent(component: DynamoNTS_EmailTemplateComponent): Promise<void> {
|
|
455
|
-
try {
|
|
456
|
-
if (this.componentsBySelector[component.selector]) {
|
|
457
|
-
throw new DynamoFM_Error({
|
|
458
|
-
...this._getDefaultErrorSettings('loadComponent', new Error(), 'SYSTEM'),
|
|
459
|
-
|
|
460
|
-
errorCode: 'NTS-ES0-LC1',
|
|
461
|
-
message: `Template already loaded! (${component.name})`,
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
if (!component.templatePath && !component.template) {
|
|
466
|
-
throw new DynamoFM_Error({
|
|
467
|
-
...this._getDefaultErrorSettings('loadComponent', new Error(), 'SYSTEM'),
|
|
468
|
-
|
|
469
|
-
errorCode: 'NTS-ES0-LC2',
|
|
470
|
-
message: `Component missing template and templatePath! (${component.name})`,
|
|
471
|
-
});
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
if (!component.template) {
|
|
475
|
-
await new Promise<void>((resolve, reject): void => {
|
|
476
|
-
FileSystem.readFile(component.templatePath, 'utf8' , (err, template): void => {
|
|
477
|
-
if (err || !template) {
|
|
478
|
-
if (!err) {
|
|
479
|
-
err = new Error(
|
|
480
|
-
`Couldn't load email component's template; ${component.name} ` +
|
|
481
|
-
`from ${component.templatePath}`
|
|
482
|
-
);
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
reject(
|
|
486
|
-
new DynamoFM_Error({
|
|
487
|
-
...this._getDefaultErrorSettings('loadTemplate', err, 'SYSTEM'),
|
|
488
|
-
|
|
489
|
-
errorCode: 'NTS-ES0-LC3',
|
|
490
|
-
})
|
|
491
|
-
);
|
|
492
|
-
|
|
493
|
-
return;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
component.template = template;
|
|
497
|
-
|
|
498
|
-
resolve();
|
|
499
|
-
});
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
if (component.stylePath) {
|
|
504
|
-
await new Promise<void>((resolve, reject): void => {
|
|
505
|
-
FileSystem.readFile(component.stylePath, 'utf8' , (err, styles): void => {
|
|
506
|
-
if (err || !styles) {
|
|
507
|
-
if (!err) {
|
|
508
|
-
err = new Error(
|
|
509
|
-
`Couldn't load email component's styles; ${component.name} ` +
|
|
510
|
-
`from ${component.stylePath}`
|
|
511
|
-
);
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
reject(
|
|
515
|
-
new DynamoFM_Error({
|
|
516
|
-
...this._getDefaultErrorSettings('loadTemplate', err, 'SYSTEM'),
|
|
517
|
-
|
|
518
|
-
errorCode: 'NTS-ES0-LC4',
|
|
519
|
-
})
|
|
520
|
-
);
|
|
521
|
-
|
|
522
|
-
return;
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
component.styles = styles;
|
|
526
|
-
|
|
527
|
-
resolve();
|
|
528
|
-
});
|
|
529
|
-
});
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
component.properties = this.getTemplatePropertyKeys(component.template);
|
|
533
|
-
this.componentsBySelector[component.selector] = component;
|
|
534
|
-
this.componentsByName[component.name] = component;
|
|
535
|
-
|
|
536
|
-
component.properties.forEach((propertyKey: string): void => {
|
|
537
|
-
if (!component.template.includes(`{{${propertyKey}}}`)) {
|
|
538
|
-
throw new DynamoFM_Error({
|
|
539
|
-
...this._getDefaultErrorSettings(
|
|
540
|
-
'loadComponent',
|
|
541
|
-
new Error(
|
|
542
|
-
`TemplateProperty missing from template! (${propertyKey} for ${component.name})`
|
|
543
|
-
),
|
|
544
|
-
'SYSTEM'
|
|
545
|
-
),
|
|
546
|
-
|
|
547
|
-
errorCode: 'NTS-ES0-LC5',
|
|
548
|
-
additionalContent: component.template,
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
});
|
|
552
|
-
} catch (error) {
|
|
553
|
-
throw new DynamoFM_Error({
|
|
554
|
-
...this._getDefaultErrorSettings('loadComponent', error, 'SYSTEM'),
|
|
555
|
-
|
|
556
|
-
errorCode: 'NTS-ES0-LC0',
|
|
557
|
-
message: `loadComponent failed! (${component.name})`,
|
|
558
|
-
});
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
private connectComponents(): void {
|
|
563
|
-
try {
|
|
564
|
-
for (const componentSelector in this.componentsBySelector) {
|
|
565
|
-
this.componentsBySelector[componentSelector].subComponentSelectors = [];
|
|
566
|
-
|
|
567
|
-
const innerTags: string[] =
|
|
568
|
-
this.componentsBySelector[componentSelector].template.split('<nts-');
|
|
569
|
-
|
|
570
|
-
innerTags.shift();
|
|
571
|
-
innerTags.forEach(
|
|
572
|
-
(subComponents: string): void => {
|
|
573
|
-
const subComponentSelector = subComponents.split('>')[0].split(' ')[0];
|
|
574
|
-
|
|
575
|
-
if (subComponentSelector !== 'content') {
|
|
576
|
-
const fullSelector = 'nts-' + subComponentSelector;
|
|
577
|
-
|
|
578
|
-
if (fullSelector) {
|
|
579
|
-
if (!this.componentsBySelector[subComponentSelector]) {
|
|
580
|
-
throw new DynamoFM_Error({
|
|
581
|
-
...this._getDefaultErrorSettings(
|
|
582
|
-
'connectComponents',
|
|
583
|
-
new Error(`SubComponent missing from components! (${fullSelector})`),
|
|
584
|
-
'SYSTEM'
|
|
585
|
-
),
|
|
586
|
-
|
|
587
|
-
errorCode: 'NTS-ES0-CC1',
|
|
588
|
-
additionalContent: {
|
|
589
|
-
availableComponenets: Object.keys(this.componentsBySelector),
|
|
590
|
-
},
|
|
591
|
-
});
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
this.componentsBySelector[componentSelector].subComponentSelectors.push(
|
|
595
|
-
subComponentSelector
|
|
596
|
-
);
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
);
|
|
601
|
-
}
|
|
602
|
-
} catch (error) {
|
|
603
|
-
throw new DynamoFM_Error({
|
|
604
|
-
...this._getDefaultErrorSettings('connectComponents', error, 'SYSTEM'),
|
|
605
|
-
|
|
606
|
-
errorCode: 'NTS-ES0-CC0',
|
|
607
|
-
message: `connectComponents failed!`,
|
|
608
|
-
});
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
/**
|
|
613
|
-
*
|
|
614
|
-
* @param template
|
|
615
|
-
* @returns
|
|
616
|
-
*/
|
|
617
|
-
private getTemplatePropertyKeys(template: string): string[] {
|
|
618
|
-
try {
|
|
619
|
-
const propertyKeys: string[] = [];
|
|
620
|
-
let propertyOpenTagIndex = template.indexOf('{{');
|
|
621
|
-
let propertyCloseTagIndex = template.indexOf('}}', propertyOpenTagIndex + 2);
|
|
622
|
-
|
|
623
|
-
while (propertyOpenTagIndex >= 0) {
|
|
624
|
-
if (propertyCloseTagIndex === -1) {
|
|
625
|
-
DynamoFM_Log.error(
|
|
626
|
-
`\nDynamoBEEmailService ERROR, missing closing tag from email template! ` +
|
|
627
|
-
`(${propertyOpenTagIndex} -)`,
|
|
628
|
-
propertyKeys
|
|
629
|
-
);
|
|
630
|
-
|
|
631
|
-
throw new DynamoFM_Error({
|
|
632
|
-
errorCode: 'NTS-ES0-200',
|
|
633
|
-
addECToUserMsg: true,
|
|
634
|
-
message: `ERROR, missing closing tag from email template! (${propertyOpenTagIndex} -)`,
|
|
635
|
-
userMessage: this.defaultErrorUserMsg,
|
|
636
|
-
});
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
const newKey: string = template.substring(propertyOpenTagIndex + 2, propertyCloseTagIndex);
|
|
640
|
-
|
|
641
|
-
if (!propertyKeys.includes(newKey)) {
|
|
642
|
-
propertyKeys.push(newKey);
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
// KELLEZIDE?
|
|
646
|
-
propertyOpenTagIndex = template.indexOf('{{', propertyOpenTagIndex + 2);
|
|
647
|
-
propertyCloseTagIndex = template.indexOf('}}', propertyOpenTagIndex + 2);
|
|
648
|
-
}
|
|
649
|
-
// console.log('\n\n\nTEST propertyKeys: ', propertyKeys);
|
|
650
|
-
|
|
651
|
-
return propertyKeys;
|
|
652
|
-
} catch (error) {
|
|
653
|
-
throw new DynamoFM_Error({
|
|
654
|
-
...this._getDefaultErrorSettings('getTemplatePropertyKeys', error, 'SYSTEM'),
|
|
655
|
-
|
|
656
|
-
errorCode: 'NTS-ES0-GTPK0',
|
|
657
|
-
message: `getTemplatePropertyKeys failed!`,
|
|
658
|
-
additionalContent: template,
|
|
659
|
-
});
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
private _getDefaultErrorSettings(
|
|
664
|
-
fnName: string,
|
|
665
|
-
error: DynamoFM_AnyError,
|
|
666
|
-
issuer: string
|
|
667
|
-
): DynamoFM_Error_Settings {
|
|
668
|
-
return {
|
|
669
|
-
status: (error as DynamoFM_Error)?.___status ?? 500,
|
|
670
|
-
message: (error as Error)?.message ?? `${fnName} was UNSUCCESFUL (NTS; ${this.serviceName})`,
|
|
671
|
-
addECToUserMsg: !(error as DynamoFM_Error)?.__userMessage,
|
|
672
|
-
userMessage: (error as DynamoFM_Error)?.__userMessage ?? this.defaultErrorUserMsg,
|
|
673
|
-
issuer: issuer,
|
|
674
|
-
issuerService: this.serviceName,
|
|
675
|
-
error: error,
|
|
676
|
-
};
|
|
677
|
-
}
|
|
678
|
-
}
|
|
1
|
+
|
|
2
|
+
import * as FileSystem from 'fs';
|
|
3
|
+
import * as Path from 'path';
|
|
4
|
+
import * as NodeMailer from 'nodemailer';
|
|
5
|
+
|
|
6
|
+
import { Options as MailOptions, Attachment } from 'nodemailer/lib/mailer';
|
|
7
|
+
|
|
8
|
+
import { DynamoFM_AnyError, DynamoFM_Array, DynamoFM_Error, DynamoFM_Error_Settings, DynamoFM_Log } from '@futdevpro/fsm-dynamo';
|
|
9
|
+
|
|
10
|
+
export interface DynamoNTS_EmailService_Settings {
|
|
11
|
+
host: string,
|
|
12
|
+
port: number,
|
|
13
|
+
email: string,
|
|
14
|
+
pass: string,
|
|
15
|
+
senderName: string,
|
|
16
|
+
rootPath: string,
|
|
17
|
+
templateComponents?: DynamoNTS_EmailTemplateComponent[],
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class DynamoNTS_EmailTemplateComponent {
|
|
21
|
+
name: string;
|
|
22
|
+
selector: string;
|
|
23
|
+
|
|
24
|
+
templatePath?: string;
|
|
25
|
+
template?: string;
|
|
26
|
+
|
|
27
|
+
stylePath?: string;
|
|
28
|
+
styles?: string;
|
|
29
|
+
|
|
30
|
+
headerLinks?: string = '';
|
|
31
|
+
|
|
32
|
+
properties?: string[] = [];
|
|
33
|
+
subComponentSelectors?: string[] = [];
|
|
34
|
+
|
|
35
|
+
constructor(
|
|
36
|
+
set?: DynamoNTS_EmailTemplateComponent
|
|
37
|
+
) {
|
|
38
|
+
if (set) {
|
|
39
|
+
Object.assign(this, set);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface DynamoNTS_SendEmail_Settings<T = { [propertyKey: string]: string; }> {
|
|
45
|
+
to: string;
|
|
46
|
+
subject: string;
|
|
47
|
+
/** direct email content, if provided, this will be used */
|
|
48
|
+
content?: string;
|
|
49
|
+
|
|
50
|
+
templateComponentName?: string;
|
|
51
|
+
templateProperties?: T;
|
|
52
|
+
|
|
53
|
+
attachments?: Attachment[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
*
|
|
58
|
+
*/
|
|
59
|
+
export class DynamoNTS_EmailService {
|
|
60
|
+
serviceName: string;
|
|
61
|
+
|
|
62
|
+
private nmTransporter: NodeMailer.Transporter;
|
|
63
|
+
private senderName: string;
|
|
64
|
+
private senderNEmail: string;
|
|
65
|
+
|
|
66
|
+
private components: DynamoNTS_EmailTemplateComponent[] = [];
|
|
67
|
+
private componentsBySelector: {
|
|
68
|
+
[selector: string]: DynamoNTS_EmailTemplateComponent
|
|
69
|
+
} = {};
|
|
70
|
+
private componentsByName: {
|
|
71
|
+
[componentName: string]: DynamoNTS_EmailTemplateComponent
|
|
72
|
+
} = {};
|
|
73
|
+
|
|
74
|
+
private readonly styleLimitWarning: number = 16;
|
|
75
|
+
|
|
76
|
+
defaultErrorUserMsg =
|
|
77
|
+
`We encountered an uncought Email Service Error, ` +
|
|
78
|
+
`\nplease contact the responsible development team.`;
|
|
79
|
+
|
|
80
|
+
constructor (
|
|
81
|
+
set: DynamoNTS_EmailService_Settings
|
|
82
|
+
) {
|
|
83
|
+
this.serviceName = this.constructor?.name;
|
|
84
|
+
this.senderName = set.senderName;
|
|
85
|
+
this.senderNEmail = `${set.senderName} <${set.email}>`;
|
|
86
|
+
this.nmTransporter = NodeMailer.createTransport({
|
|
87
|
+
host: set.host,
|
|
88
|
+
port: set.port,
|
|
89
|
+
auth: {
|
|
90
|
+
user: set.email,
|
|
91
|
+
pass: set.pass,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
this.components = set.templateComponents ?? [];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async asyncPostConstruct(): Promise<void> {
|
|
99
|
+
try {
|
|
100
|
+
if (this.components) {
|
|
101
|
+
await DynamoFM_Array.asyncForEach(
|
|
102
|
+
this.components,
|
|
103
|
+
async (component: DynamoNTS_EmailTemplateComponent): Promise<void> => {
|
|
104
|
+
await this.loadComponent(component);
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
this.connectComponents();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
DynamoFM_Log.success(`\nEmailService construction (${this.senderName}) Finished!`);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
throw new DynamoFM_Error({
|
|
114
|
+
...this._getDefaultErrorSettings('asyncPostConstruct', error, 'SYSTEM'),
|
|
115
|
+
|
|
116
|
+
errorCode: 'NTS-ES0-APC0',
|
|
117
|
+
message:
|
|
118
|
+
`\nDynamoBEEmailService ERROR, ` +
|
|
119
|
+
`\nThe emailService construction failed for ${this.serviceName}.`,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
*
|
|
126
|
+
* @param set
|
|
127
|
+
*/
|
|
128
|
+
public async sendEmail<T>(
|
|
129
|
+
set: DynamoNTS_SendEmail_Settings<T>,
|
|
130
|
+
issuer: string
|
|
131
|
+
): Promise<void> {
|
|
132
|
+
try {
|
|
133
|
+
let content: string;
|
|
134
|
+
|
|
135
|
+
if (set.content) {
|
|
136
|
+
content = set.content;
|
|
137
|
+
|
|
138
|
+
} else {
|
|
139
|
+
if (!set.templateComponentName) {
|
|
140
|
+
throw new DynamoFM_Error({
|
|
141
|
+
...this._getDefaultErrorSettings(
|
|
142
|
+
'sendEmail',
|
|
143
|
+
new Error(
|
|
144
|
+
`No email template component is given!`
|
|
145
|
+
),
|
|
146
|
+
issuer
|
|
147
|
+
),
|
|
148
|
+
|
|
149
|
+
errorCode: 'NTS-ES0-SE1',
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!this.componentsByName[set.templateComponentName]) {
|
|
154
|
+
throw new DynamoFM_Error({
|
|
155
|
+
...this._getDefaultErrorSettings(
|
|
156
|
+
'sendEmail',
|
|
157
|
+
new Error(
|
|
158
|
+
`No email template component found with this name! (${set.templateComponentName})`
|
|
159
|
+
),
|
|
160
|
+
issuer
|
|
161
|
+
),
|
|
162
|
+
|
|
163
|
+
errorCode: 'NTS-ES0-SE2',
|
|
164
|
+
additionalContent: {
|
|
165
|
+
availableComponenets: Object.keys(this.componentsByName),
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
content = this.compileTemplateComponent(
|
|
171
|
+
set.templateComponentName,
|
|
172
|
+
set.templateProperties,
|
|
173
|
+
issuer
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const mailOptions: MailOptions = {
|
|
178
|
+
from: this.senderNEmail,
|
|
179
|
+
to: set.to,
|
|
180
|
+
subject: set.subject,
|
|
181
|
+
html: content,
|
|
182
|
+
attachments: set.attachments,
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
await new Promise<void>((resolve, reject): void => {
|
|
186
|
+
this.nmTransporter.sendMail(mailOptions, (error): void => {
|
|
187
|
+
if (error) {
|
|
188
|
+
reject(error);
|
|
189
|
+
} else {
|
|
190
|
+
resolve();
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
} catch (error) {
|
|
195
|
+
if ((error as Error).message?.includes?.('all recipients were rejected')) {
|
|
196
|
+
throw new DynamoFM_Error({
|
|
197
|
+
...this._getDefaultErrorSettings('sendEmail', error, issuer),
|
|
198
|
+
|
|
199
|
+
addECToUserMsg: false,
|
|
200
|
+
errorCode: 'NTS-ES0-SE4',
|
|
201
|
+
userMessage: `Can't send mail to ${set.to}!`,
|
|
202
|
+
message: `sendEmail failed to ${set.to}`,
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
} else {
|
|
206
|
+
throw new DynamoFM_Error({
|
|
207
|
+
...this._getDefaultErrorSettings('sendEmail', error, issuer),
|
|
208
|
+
|
|
209
|
+
errorCode: 'NTS-ES0-SE0',
|
|
210
|
+
message: `sendEmail failed to ${set.to}`,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private compileTemplateComponent<T>(
|
|
217
|
+
componentName: string,
|
|
218
|
+
componentProperties:T,
|
|
219
|
+
issuer: string
|
|
220
|
+
): string {
|
|
221
|
+
try {
|
|
222
|
+
if (!this.componentsByName[componentName]) {
|
|
223
|
+
throw new DynamoFM_Error({
|
|
224
|
+
...this._getDefaultErrorSettings(
|
|
225
|
+
'setupComponent',
|
|
226
|
+
new Error(`No email component found with this name! (${componentName})`),
|
|
227
|
+
issuer
|
|
228
|
+
),
|
|
229
|
+
|
|
230
|
+
errorCode: 'NTS-ES0-SC1',
|
|
231
|
+
additionalContent: {
|
|
232
|
+
availableComponenets: Object.keys(this.componentsByName),
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const component: DynamoNTS_EmailTemplateComponent = this.componentsByName[componentName];
|
|
238
|
+
let template: string = this.compileHTMLMainFrame(component);
|
|
239
|
+
|
|
240
|
+
template = template.replace('<nts-content>', '');
|
|
241
|
+
template = this.compileComponentProperties<T>(
|
|
242
|
+
component,
|
|
243
|
+
componentProperties,
|
|
244
|
+
template,
|
|
245
|
+
issuer
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
component.subComponentSelectors.forEach((subComponentSelector: string): void => {
|
|
249
|
+
while (template.includes(`<nts-${subComponentSelector}`)) {
|
|
250
|
+
template = this.compileSubComponent<T>(
|
|
251
|
+
subComponentSelector, componentProperties, template, issuer
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
template = this.templateTrim(template);
|
|
257
|
+
|
|
258
|
+
return template;
|
|
259
|
+
} catch (error) {
|
|
260
|
+
throw new DynamoFM_Error({
|
|
261
|
+
...this._getDefaultErrorSettings('setupComponent', error, issuer),
|
|
262
|
+
|
|
263
|
+
errorCode: 'NTS-ES0-SC0',
|
|
264
|
+
message: `setupComponent failed! (${componentName})`,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private compileHTMLMainFrame(component: DynamoNTS_EmailTemplateComponent): string {
|
|
270
|
+
const allLinks: string[] = [];
|
|
271
|
+
const allStyles: string[] = [];
|
|
272
|
+
|
|
273
|
+
if (component.headerLinks) {
|
|
274
|
+
allLinks.push(component.headerLinks);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (component.styles) {
|
|
278
|
+
allStyles.push(component.styles);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
component.subComponentSelectors.forEach((subComponentSelector: string): void => {
|
|
282
|
+
const subComponent: DynamoNTS_EmailTemplateComponent =
|
|
283
|
+
this.componentsBySelector[subComponentSelector];
|
|
284
|
+
|
|
285
|
+
if (subComponent.headerLinks) {
|
|
286
|
+
allLinks.push(subComponent.headerLinks.trim());
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (subComponent.styles) {
|
|
290
|
+
allStyles.push(subComponent.styles.trim());
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
const styles = this.styleTrim(allStyles.join(''));
|
|
295
|
+
const styleSize = this.getStringKBSize(styles);
|
|
296
|
+
|
|
297
|
+
if (this.styleLimitWarning < styleSize) {
|
|
298
|
+
DynamoFM_Log.warn(
|
|
299
|
+
`\nDynamoNTS EmailService WARNING (${this.serviceName}), ` +
|
|
300
|
+
`\nEmail template styles are too big! (${styleSize}KB)` +
|
|
301
|
+
`\ncomponent: ${component.name} (${component.selector})` +
|
|
302
|
+
`\nSome email clients may not support this!` +
|
|
303
|
+
`\nThe limit is ${this.styleLimitWarning}KB!`
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return `\n<!DOCTYPE html>` +
|
|
308
|
+
`\n<html>` +
|
|
309
|
+
`\n <head>` +
|
|
310
|
+
`\n ${allLinks.join('')}` +
|
|
311
|
+
`\n` +
|
|
312
|
+
`\n <style>` +
|
|
313
|
+
`\n ${styles}` +
|
|
314
|
+
`\n </style>` +
|
|
315
|
+
`\n </head>` +
|
|
316
|
+
`\n <body>` +
|
|
317
|
+
`\n ${component.template}` +
|
|
318
|
+
`\n </body>` +
|
|
319
|
+
`\n</html> `;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** trims all rows and remove everything between /*...*\/ */
|
|
323
|
+
private styleTrim(style: string): string {
|
|
324
|
+
let result: string = style.split('\n').map((row: string): string => row.trim()).join('');
|
|
325
|
+
|
|
326
|
+
while (result.includes('/*')) {
|
|
327
|
+
const start = result.indexOf('/*');
|
|
328
|
+
const end = result.indexOf('*/', start) + 2;
|
|
329
|
+
|
|
330
|
+
result = result.substring(0, start) + result.substring(end);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return result;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** removes HTML comments */
|
|
337
|
+
private templateTrim(template: string): string {
|
|
338
|
+
let result: string = template;
|
|
339
|
+
|
|
340
|
+
while (result.includes('<!--')) {
|
|
341
|
+
const start = result.indexOf('<!--');
|
|
342
|
+
const end = result.indexOf('-->', start) + 3;
|
|
343
|
+
|
|
344
|
+
result = result.substring(0, start) + result.substring(end);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return result;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
private getStringKBSize(str: string): number {
|
|
352
|
+
const b: number = str.length * 2;
|
|
353
|
+
|
|
354
|
+
return b / 1024;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
private compileComponentProperties<T>(
|
|
358
|
+
component: DynamoNTS_EmailTemplateComponent,
|
|
359
|
+
componentProperties: T,
|
|
360
|
+
template: string,
|
|
361
|
+
issuer: string
|
|
362
|
+
): string {
|
|
363
|
+
component.properties.forEach((propertyKey: string): void => {
|
|
364
|
+
if (!componentProperties[propertyKey]) {
|
|
365
|
+
throw new DynamoFM_Error({
|
|
366
|
+
...this._getDefaultErrorSettings(
|
|
367
|
+
'setupComponent',
|
|
368
|
+
new Error(
|
|
369
|
+
`ComponentProperty missing from input! '${propertyKey}' for ${component.name}`
|
|
370
|
+
),
|
|
371
|
+
issuer
|
|
372
|
+
),
|
|
373
|
+
|
|
374
|
+
errorCode: 'NTS-ES0-SC4',
|
|
375
|
+
additionalContent: {
|
|
376
|
+
componentProperties: Object.keys(componentProperties),
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const propReg = new RegExp(`{{${propertyKey}}}`, 'g');
|
|
382
|
+
|
|
383
|
+
template = template.replace(propReg, componentProperties[propertyKey]);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
return template;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
private compileSubComponent<T>(
|
|
390
|
+
subComponentSelector: string,
|
|
391
|
+
componentProperties: T,
|
|
392
|
+
template: string,
|
|
393
|
+
issuer: string
|
|
394
|
+
): string {
|
|
395
|
+
const subComponent: DynamoNTS_EmailTemplateComponent =
|
|
396
|
+
this.componentsBySelector[subComponentSelector];
|
|
397
|
+
|
|
398
|
+
if (!subComponent) {
|
|
399
|
+
throw new DynamoFM_Error({
|
|
400
|
+
...this._getDefaultErrorSettings(
|
|
401
|
+
'setupComponent',
|
|
402
|
+
new Error(`SubComponent missing from components! (${subComponentSelector})`),
|
|
403
|
+
issuer
|
|
404
|
+
),
|
|
405
|
+
|
|
406
|
+
errorCode: 'NTS-ES0-SC2',
|
|
407
|
+
additionalContent: this.componentsBySelector,
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
let subComponentTemplate: string = subComponent.template;
|
|
412
|
+
const startTag = `<nts-${subComponent.selector}`;
|
|
413
|
+
const endTag = `</nts-${subComponent.selector}>`;
|
|
414
|
+
const tagHTMLProperties: string =
|
|
415
|
+
template.split(startTag)[1].split('>')[0];
|
|
416
|
+
let componentContent: string;
|
|
417
|
+
|
|
418
|
+
if (!tagHTMLProperties) {
|
|
419
|
+
componentContent = template.split(`${startTag}>`)[1].split(endTag)[0];
|
|
420
|
+
} else {
|
|
421
|
+
componentContent = template.split(startTag)[1].split('>')[1].split(endTag)[0];
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const contentReg = new RegExp(`<nts-content>`, 'g');
|
|
425
|
+
|
|
426
|
+
subComponentTemplate = subComponentTemplate.replace(contentReg, componentContent);
|
|
427
|
+
subComponentTemplate = this.compileComponentProperties<T>(
|
|
428
|
+
subComponent,
|
|
429
|
+
componentProperties,
|
|
430
|
+
subComponentTemplate,
|
|
431
|
+
issuer
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
const startTagReg = new RegExp(startTag, 'g');
|
|
435
|
+
const endTagReg = new RegExp(endTag, 'g');
|
|
436
|
+
|
|
437
|
+
template = template.replace(startTagReg, '<div');
|
|
438
|
+
template = template.replace(componentContent, subComponentTemplate);
|
|
439
|
+
template = template.replace(endTagReg, '</div>');
|
|
440
|
+
|
|
441
|
+
/* while (template.includes(`<nts-${subComponent.selector}`)) {
|
|
442
|
+
const replaceStartIndex = template.indexOf(`<nts-${subComponent.selector}`);
|
|
443
|
+
const replaceEndIndex = template.indexOf(endTag, replaceStartIndex) + endTag.length;
|
|
444
|
+
|
|
445
|
+
template =
|
|
446
|
+
template.substring(0, replaceStartIndex) +
|
|
447
|
+
subComponentCompiled +
|
|
448
|
+
template.substring(replaceEndIndex);
|
|
449
|
+
} */
|
|
450
|
+
|
|
451
|
+
return template;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
private async loadComponent(component: DynamoNTS_EmailTemplateComponent): Promise<void> {
|
|
455
|
+
try {
|
|
456
|
+
if (this.componentsBySelector[component.selector]) {
|
|
457
|
+
throw new DynamoFM_Error({
|
|
458
|
+
...this._getDefaultErrorSettings('loadComponent', new Error(), 'SYSTEM'),
|
|
459
|
+
|
|
460
|
+
errorCode: 'NTS-ES0-LC1',
|
|
461
|
+
message: `Template already loaded! (${component.name})`,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (!component.templatePath && !component.template) {
|
|
466
|
+
throw new DynamoFM_Error({
|
|
467
|
+
...this._getDefaultErrorSettings('loadComponent', new Error(), 'SYSTEM'),
|
|
468
|
+
|
|
469
|
+
errorCode: 'NTS-ES0-LC2',
|
|
470
|
+
message: `Component missing template and templatePath! (${component.name})`,
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (!component.template) {
|
|
475
|
+
await new Promise<void>((resolve, reject): void => {
|
|
476
|
+
FileSystem.readFile(component.templatePath, 'utf8' , (err, template): void => {
|
|
477
|
+
if (err || !template) {
|
|
478
|
+
if (!err) {
|
|
479
|
+
err = new Error(
|
|
480
|
+
`Couldn't load email component's template; ${component.name} ` +
|
|
481
|
+
`from ${component.templatePath}`
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
reject(
|
|
486
|
+
new DynamoFM_Error({
|
|
487
|
+
...this._getDefaultErrorSettings('loadTemplate', err, 'SYSTEM'),
|
|
488
|
+
|
|
489
|
+
errorCode: 'NTS-ES0-LC3',
|
|
490
|
+
})
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
component.template = template;
|
|
497
|
+
|
|
498
|
+
resolve();
|
|
499
|
+
});
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (component.stylePath) {
|
|
504
|
+
await new Promise<void>((resolve, reject): void => {
|
|
505
|
+
FileSystem.readFile(component.stylePath, 'utf8' , (err, styles): void => {
|
|
506
|
+
if (err || !styles) {
|
|
507
|
+
if (!err) {
|
|
508
|
+
err = new Error(
|
|
509
|
+
`Couldn't load email component's styles; ${component.name} ` +
|
|
510
|
+
`from ${component.stylePath}`
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
reject(
|
|
515
|
+
new DynamoFM_Error({
|
|
516
|
+
...this._getDefaultErrorSettings('loadTemplate', err, 'SYSTEM'),
|
|
517
|
+
|
|
518
|
+
errorCode: 'NTS-ES0-LC4',
|
|
519
|
+
})
|
|
520
|
+
);
|
|
521
|
+
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
component.styles = styles;
|
|
526
|
+
|
|
527
|
+
resolve();
|
|
528
|
+
});
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
component.properties = this.getTemplatePropertyKeys(component.template);
|
|
533
|
+
this.componentsBySelector[component.selector] = component;
|
|
534
|
+
this.componentsByName[component.name] = component;
|
|
535
|
+
|
|
536
|
+
component.properties.forEach((propertyKey: string): void => {
|
|
537
|
+
if (!component.template.includes(`{{${propertyKey}}}`)) {
|
|
538
|
+
throw new DynamoFM_Error({
|
|
539
|
+
...this._getDefaultErrorSettings(
|
|
540
|
+
'loadComponent',
|
|
541
|
+
new Error(
|
|
542
|
+
`TemplateProperty missing from template! (${propertyKey} for ${component.name})`
|
|
543
|
+
),
|
|
544
|
+
'SYSTEM'
|
|
545
|
+
),
|
|
546
|
+
|
|
547
|
+
errorCode: 'NTS-ES0-LC5',
|
|
548
|
+
additionalContent: component.template,
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
} catch (error) {
|
|
553
|
+
throw new DynamoFM_Error({
|
|
554
|
+
...this._getDefaultErrorSettings('loadComponent', error, 'SYSTEM'),
|
|
555
|
+
|
|
556
|
+
errorCode: 'NTS-ES0-LC0',
|
|
557
|
+
message: `loadComponent failed! (${component.name})`,
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
private connectComponents(): void {
|
|
563
|
+
try {
|
|
564
|
+
for (const componentSelector in this.componentsBySelector) {
|
|
565
|
+
this.componentsBySelector[componentSelector].subComponentSelectors = [];
|
|
566
|
+
|
|
567
|
+
const innerTags: string[] =
|
|
568
|
+
this.componentsBySelector[componentSelector].template.split('<nts-');
|
|
569
|
+
|
|
570
|
+
innerTags.shift();
|
|
571
|
+
innerTags.forEach(
|
|
572
|
+
(subComponents: string): void => {
|
|
573
|
+
const subComponentSelector = subComponents.split('>')[0].split(' ')[0];
|
|
574
|
+
|
|
575
|
+
if (subComponentSelector !== 'content') {
|
|
576
|
+
const fullSelector = 'nts-' + subComponentSelector;
|
|
577
|
+
|
|
578
|
+
if (fullSelector) {
|
|
579
|
+
if (!this.componentsBySelector[subComponentSelector]) {
|
|
580
|
+
throw new DynamoFM_Error({
|
|
581
|
+
...this._getDefaultErrorSettings(
|
|
582
|
+
'connectComponents',
|
|
583
|
+
new Error(`SubComponent missing from components! (${fullSelector})`),
|
|
584
|
+
'SYSTEM'
|
|
585
|
+
),
|
|
586
|
+
|
|
587
|
+
errorCode: 'NTS-ES0-CC1',
|
|
588
|
+
additionalContent: {
|
|
589
|
+
availableComponenets: Object.keys(this.componentsBySelector),
|
|
590
|
+
},
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
this.componentsBySelector[componentSelector].subComponentSelectors.push(
|
|
595
|
+
subComponentSelector
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
} catch (error) {
|
|
603
|
+
throw new DynamoFM_Error({
|
|
604
|
+
...this._getDefaultErrorSettings('connectComponents', error, 'SYSTEM'),
|
|
605
|
+
|
|
606
|
+
errorCode: 'NTS-ES0-CC0',
|
|
607
|
+
message: `connectComponents failed!`,
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
*
|
|
614
|
+
* @param template
|
|
615
|
+
* @returns
|
|
616
|
+
*/
|
|
617
|
+
private getTemplatePropertyKeys(template: string): string[] {
|
|
618
|
+
try {
|
|
619
|
+
const propertyKeys: string[] = [];
|
|
620
|
+
let propertyOpenTagIndex = template.indexOf('{{');
|
|
621
|
+
let propertyCloseTagIndex = template.indexOf('}}', propertyOpenTagIndex + 2);
|
|
622
|
+
|
|
623
|
+
while (propertyOpenTagIndex >= 0) {
|
|
624
|
+
if (propertyCloseTagIndex === -1) {
|
|
625
|
+
DynamoFM_Log.error(
|
|
626
|
+
`\nDynamoBEEmailService ERROR, missing closing tag from email template! ` +
|
|
627
|
+
`(${propertyOpenTagIndex} -)`,
|
|
628
|
+
propertyKeys
|
|
629
|
+
);
|
|
630
|
+
|
|
631
|
+
throw new DynamoFM_Error({
|
|
632
|
+
errorCode: 'NTS-ES0-200',
|
|
633
|
+
addECToUserMsg: true,
|
|
634
|
+
message: `ERROR, missing closing tag from email template! (${propertyOpenTagIndex} -)`,
|
|
635
|
+
userMessage: this.defaultErrorUserMsg,
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const newKey: string = template.substring(propertyOpenTagIndex + 2, propertyCloseTagIndex);
|
|
640
|
+
|
|
641
|
+
if (!propertyKeys.includes(newKey)) {
|
|
642
|
+
propertyKeys.push(newKey);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// KELLEZIDE?
|
|
646
|
+
propertyOpenTagIndex = template.indexOf('{{', propertyOpenTagIndex + 2);
|
|
647
|
+
propertyCloseTagIndex = template.indexOf('}}', propertyOpenTagIndex + 2);
|
|
648
|
+
}
|
|
649
|
+
// console.log('\n\n\nTEST propertyKeys: ', propertyKeys);
|
|
650
|
+
|
|
651
|
+
return propertyKeys;
|
|
652
|
+
} catch (error) {
|
|
653
|
+
throw new DynamoFM_Error({
|
|
654
|
+
...this._getDefaultErrorSettings('getTemplatePropertyKeys', error, 'SYSTEM'),
|
|
655
|
+
|
|
656
|
+
errorCode: 'NTS-ES0-GTPK0',
|
|
657
|
+
message: `getTemplatePropertyKeys failed!`,
|
|
658
|
+
additionalContent: template,
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
private _getDefaultErrorSettings(
|
|
664
|
+
fnName: string,
|
|
665
|
+
error: DynamoFM_AnyError,
|
|
666
|
+
issuer: string
|
|
667
|
+
): DynamoFM_Error_Settings {
|
|
668
|
+
return {
|
|
669
|
+
status: (error as DynamoFM_Error)?.___status ?? 500,
|
|
670
|
+
message: (error as Error)?.message ?? `${fnName} was UNSUCCESFUL (NTS; ${this.serviceName})`,
|
|
671
|
+
addECToUserMsg: !(error as DynamoFM_Error)?.__userMessage,
|
|
672
|
+
userMessage: (error as DynamoFM_Error)?.__userMessage ?? this.defaultErrorUserMsg,
|
|
673
|
+
issuer: issuer,
|
|
674
|
+
issuerService: this.serviceName,
|
|
675
|
+
error: error,
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
}
|