@deenruv/email-plugin 1.0.0

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.
Files changed (76) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +85 -0
  3. package/dev-mailbox.html +218 -0
  4. package/lib/index.d.ts +13 -0
  5. package/lib/index.js +30 -0
  6. package/lib/index.js.map +1 -0
  7. package/lib/src/attachment-utils.d.ts +3 -0
  8. package/lib/src/attachment-utils.js +66 -0
  9. package/lib/src/attachment-utils.js.map +1 -0
  10. package/lib/src/common.d.ts +4 -0
  11. package/lib/src/common.js +17 -0
  12. package/lib/src/common.js.map +1 -0
  13. package/lib/src/constants.d.ts +2 -0
  14. package/lib/src/constants.js +6 -0
  15. package/lib/src/constants.js.map +1 -0
  16. package/lib/src/dev-mailbox.d.ts +14 -0
  17. package/lib/src/dev-mailbox.js +116 -0
  18. package/lib/src/dev-mailbox.js.map +1 -0
  19. package/lib/src/email-processor.d.ts +21 -0
  20. package/lib/src/email-processor.js +109 -0
  21. package/lib/src/email-processor.js.map +1 -0
  22. package/lib/src/email-send-event.d.ts +18 -0
  23. package/lib/src/email-send-event.js +24 -0
  24. package/lib/src/email-send-event.js.map +1 -0
  25. package/lib/src/event-listener.d.ts +19 -0
  26. package/lib/src/event-listener.js +25 -0
  27. package/lib/src/event-listener.js.map +1 -0
  28. package/lib/src/generator/email-generator.d.ts +25 -0
  29. package/lib/src/generator/email-generator.js +3 -0
  30. package/lib/src/generator/email-generator.js.map +1 -0
  31. package/lib/src/generator/handlebars-mjml-generator.d.ts +19 -0
  32. package/lib/src/generator/handlebars-mjml-generator.js +78 -0
  33. package/lib/src/generator/handlebars-mjml-generator.js.map +1 -0
  34. package/lib/src/generator/noop-email-generator.d.ts +11 -0
  35. package/lib/src/generator/noop-email-generator.js +13 -0
  36. package/lib/src/generator/noop-email-generator.js.map +1 -0
  37. package/lib/src/generator/react-email-generator.d.ts +7 -0
  38. package/lib/src/generator/react-email-generator.js +40 -0
  39. package/lib/src/generator/react-email-generator.js.map +1 -0
  40. package/lib/src/handler/default-email-handlers.d.ts +32 -0
  41. package/lib/src/handler/default-email-handlers.js +111 -0
  42. package/lib/src/handler/default-email-handlers.js.map +1 -0
  43. package/lib/src/handler/event-handler.d.ts +276 -0
  44. package/lib/src/handler/event-handler.js +396 -0
  45. package/lib/src/handler/event-handler.js.map +1 -0
  46. package/lib/src/handler/mock-events.d.ts +5 -0
  47. package/lib/src/handler/mock-events.js +119 -0
  48. package/lib/src/handler/mock-events.js.map +1 -0
  49. package/lib/src/plugin.d.ts +301 -0
  50. package/lib/src/plugin.js +428 -0
  51. package/lib/src/plugin.js.map +1 -0
  52. package/lib/src/sender/email-sender.d.ts +45 -0
  53. package/lib/src/sender/email-sender.js +3 -0
  54. package/lib/src/sender/email-sender.js.map +1 -0
  55. package/lib/src/sender/nodemailer-email-sender.d.ts +37 -0
  56. package/lib/src/sender/nodemailer-email-sender.js +151 -0
  57. package/lib/src/sender/nodemailer-email-sender.js.map +1 -0
  58. package/lib/src/template-loader/file-based-template-loader.d.ts +17 -0
  59. package/lib/src/template-loader/file-based-template-loader.js +37 -0
  60. package/lib/src/template-loader/file-based-template-loader.js.map +1 -0
  61. package/lib/src/template-loader/react-email-template-loader.d.ts +6 -0
  62. package/lib/src/template-loader/react-email-template-loader.js +14 -0
  63. package/lib/src/template-loader/react-email-template-loader.js.map +1 -0
  64. package/lib/src/template-loader/template-loader.d.ts +42 -0
  65. package/lib/src/template-loader/template-loader.js +3 -0
  66. package/lib/src/template-loader/template-loader.js.map +1 -0
  67. package/lib/src/types.d.ts +453 -0
  68. package/lib/src/types.js +3 -0
  69. package/lib/src/types.js.map +1 -0
  70. package/package.json +51 -0
  71. package/templates/email-address-change/body.hbs +20 -0
  72. package/templates/email-verification/body.hbs +20 -0
  73. package/templates/order-confirmation/body.hbs +133 -0
  74. package/templates/partials/footer.hbs +10 -0
  75. package/templates/partials/header.hbs +18 -0
  76. package/templates/password-reset/body.hbs +24 -0
@@ -0,0 +1,276 @@
1
+ import { LanguageCode } from "@deenruv/common/lib/generated-types";
2
+ import { Type } from "@deenruv/common/lib/shared-types";
3
+ import { Injector } from "@deenruv/core";
4
+ import { EmailEventListener } from "../event-listener";
5
+ import { EmailTemplateConfig, EventWithAsyncData, EventWithContext, IntermediateEmailDetails, LoadDataFn, SetAttachmentsFn, SetOptionalAddressFieldsFn, SetSubjectFn, SetTemplateVarsFn } from "../types";
6
+ /**
7
+ * @description
8
+ * The EmailEventHandler defines how the EmailPlugin will respond to a given event.
9
+ *
10
+ * A handler is created by creating a new {@link EmailEventListener} and calling the `.on()` method
11
+ * to specify which event to respond to.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * const confirmationHandler = new EmailEventListener('order-confirmation')
16
+ * .on(OrderStateTransitionEvent)
17
+ * .filter(event => event.toState === 'PaymentSettled')
18
+ * .setRecipient(event => event.order.customer.emailAddress)
19
+ * .setFrom('{{ fromAddress }}')
20
+ * .setSubject(`Order confirmation for #{{ order.code }}`)
21
+ * .setTemplateVars(event => ({ order: event.order }));
22
+ * ```
23
+ *
24
+ * This example creates a handler which listens for the `OrderStateTransitionEvent` and if the Order has
25
+ * transitioned to the `'PaymentSettled'` state, it will generate and send an email.
26
+ *
27
+ * The string argument passed into the `EmailEventListener` constructor is used to identify the handler, and
28
+ * also to locate the directory of the email template files. So in the example above, there should be a directory
29
+ * `<app root>/static/email/templates/order-confirmation` which contains a Handlebars template named `body.hbs`.
30
+ *
31
+ * ## Handling other languages
32
+ *
33
+ * By default, the handler will respond to all events on all channels and use the same subject ("Order confirmation for #12345" above)
34
+ * and body template. Where the server is intended to support multiple languages, the `.addTemplate()` method may be used
35
+ * to define the subject and body template for specific language and channel combinations.
36
+ *
37
+ * The language is determined by looking at the `languageCode` property of the event's `ctx` ({@link RequestContext}) object.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * const extendedConfirmationHandler = confirmationHandler
42
+ * .addTemplate({
43
+ * channelCode: 'default',
44
+ * languageCode: LanguageCode.de,
45
+ * templateFile: 'body.de.hbs',
46
+ * subject: 'Bestellbestätigung für #{{ order.code }}',
47
+ * })
48
+ * ```
49
+ *
50
+ * ## Defining a custom handler
51
+ *
52
+ * Let's say you have a plugin which defines a new event type, `QuoteRequestedEvent`. In your plugin you have defined a mutation
53
+ * which is executed when the customer requests a quote in your storefront, and in your resolver, you use the {@link EventBus} to publish a
54
+ * new `QuoteRequestedEvent`.
55
+ *
56
+ * You now want to email the customer with their quote. Here are the steps you would take to set this up:
57
+ *
58
+ * ### 1. Create a new handler
59
+ *
60
+ * ```ts
61
+ * import { EmailEventListener } from `\@deenruv/email-plugin`;
62
+ * import { QuoteRequestedEvent } from `./events`;
63
+ *
64
+ * const quoteRequestedHandler = new EmailEventListener('quote-requested')
65
+ * .on(QuoteRequestedEvent)
66
+ * .setRecipient(event => event.customer.emailAddress)
67
+ * .setSubject(`Here's the quote you requested`)
68
+ * .setFrom('{{ fromAddress }}')
69
+ * .setTemplateVars(event => ({ details: event.details }));
70
+ * ```
71
+ *
72
+ * ### 2. Create the email template
73
+ *
74
+ * Next you need to make sure there is a template defined at `<app root>/static/email/templates/quote-requested/body.hbs`. The path
75
+ * segment `quote-requested` must match the string passed to the `EmailEventListener` constructor.
76
+ *
77
+ * The template would look something like this:
78
+ *
79
+ * ```handlebars
80
+ * {{> header title="Here's the quote you requested" }}
81
+ *
82
+ * <mj-section background-color="#fafafa">
83
+ * <mj-column>
84
+ * <mj-text color="#525252">
85
+ * Thank you for your interest in our products! Here's the details
86
+ * of the quote you recently requested:
87
+ * </mj-text>
88
+ *
89
+ * <--! your custom email layout goes here -->
90
+ * </mj-column>
91
+ * </mj-section>
92
+ *
93
+ *
94
+ * {{> footer }}
95
+ * ```
96
+ *
97
+ * You can find pre-made templates on the [MJML website](https://mjml.io/templates/).
98
+ *
99
+ * ### 3. Register the handler
100
+ *
101
+ * Finally, you need to register the handler with the EmailPlugin:
102
+ *
103
+ * ```ts {hl_lines=[8]}
104
+ * import { defaultEmailHandlers, EmailPlugin } from '\@deenruv/email-plugin';
105
+ * import { quoteRequestedHandler } from './plugins/quote-plugin';
106
+ *
107
+ * const config: DeenruvConfig = {
108
+ * // Add an instance of the plugin to the plugins array
109
+ * plugins: [
110
+ * EmailPlugin.init({
111
+ * handler: [...defaultEmailHandlers, quoteRequestedHandler],
112
+ * templatePath: path.join(__dirname, 'deenruv/email/templates'),
113
+ * // ... etc
114
+ * }),
115
+ * ],
116
+ * };
117
+ * ```
118
+ *
119
+ * @docsCategory core plugins/EmailPlugin
120
+ */
121
+ export declare class EmailEventHandler<T extends string = string, Event extends EventWithContext = EventWithContext> {
122
+ listener: EmailEventListener<T>;
123
+ event: Type<Event>;
124
+ private setRecipientFn;
125
+ private setLanguageCodeFn;
126
+ private setSubjectFn?;
127
+ private setTemplateVarsFn;
128
+ private setAttachmentsFn?;
129
+ private setOptionalAddressFieldsFn?;
130
+ private filterFns;
131
+ private configurations;
132
+ private defaultSubject;
133
+ private from;
134
+ private optionalAddressFields;
135
+ private _mockEvent;
136
+ constructor(listener: EmailEventListener<T>, event: Type<Event>);
137
+ /** @internal */
138
+ get type(): T;
139
+ /** @internal */
140
+ get mockEvent(): Omit<Event, "ctx" | "data"> | undefined;
141
+ /**
142
+ * @description
143
+ * Defines a predicate function which is used to determine whether the event will trigger an email.
144
+ * Multiple filter functions may be defined.
145
+ */
146
+ filter(filterFn: (event: Event) => boolean): EmailEventHandler<T, Event>;
147
+ /**
148
+ * @description
149
+ * A function which defines how the recipient email address should be extracted from the incoming event.
150
+ *
151
+ * The recipient can be a plain email address: `'foobar@example.com'`
152
+ * Or with a formatted name (includes unicode support): `'Ноде Майлер <foobar@example.com>'`
153
+ * Or a comma-separated list of addresses: `'foobar@example.com, "Ноде Майлер" <bar@example.com>'`
154
+ */
155
+ setRecipient(setRecipientFn: (event: Event) => string): EmailEventHandler<T, Event>;
156
+ /**
157
+ * @description
158
+ * A function which allows to override the language of the email. If not defined, the language from the context will be used.
159
+ *
160
+ * @since 1.8.0
161
+ */
162
+ setLanguageCode(setLanguageCodeFn: (event: Event) => LanguageCode | undefined): EmailEventHandler<T, Event>;
163
+ /**
164
+ * @description
165
+ * A function which returns an object hash of variables which will be made available to the Handlebars template
166
+ * and subject line for interpolation.
167
+ */
168
+ setTemplateVars(templateVarsFn: SetTemplateVarsFn<Event>): EmailEventHandler<T, Event>;
169
+ /**
170
+ * @description
171
+ * Sets the default subject of the email. The subject string may use Handlebars variables defined by the
172
+ * setTemplateVars() method.
173
+ */
174
+ setSubject(defaultSubject: string | SetSubjectFn<Event>): EmailEventHandler<T, Event>;
175
+ /**
176
+ * @description
177
+ * Sets the default from field of the email. The from string may use Handlebars variables defined by the
178
+ * setTemplateVars() method.
179
+ */
180
+ setFrom(from: string): EmailEventHandler<T, Event>;
181
+ /**
182
+ * @description
183
+ * A function which allows {@link OptionalAddressFields} to be specified such as "cc" and "bcc".
184
+ *
185
+ * @since 1.1.0
186
+ */
187
+ setOptionalAddressFields(optionalAddressFieldsFn: SetOptionalAddressFieldsFn<Event>): this;
188
+ /**
189
+ * @description
190
+ * Defines one or more files to be attached to the email. An attachment can be specified
191
+ * as either a `path` (to a file or URL) or as `content` which can be a string, Buffer or Stream.
192
+ *
193
+ * **Note:** When using the `content` to pass a Buffer or Stream, the raw data will get serialized
194
+ * into the job queue. For this reason the total size of all attachments passed as `content` should kept to
195
+ * **less than ~50k**. If the attachments are greater than that limit, a warning will be logged and
196
+ * errors may result if using the DefaultJobQueuePlugin with certain DBs such as MySQL/MariaDB.
197
+ *
198
+ * @example
199
+ * ```ts
200
+ * const testAttachmentHandler = new EmailEventListener('activate-voucher')
201
+ * .on(ActivateVoucherEvent)
202
+ * // ... omitted some steps for brevity
203
+ * .setAttachments(async (event) => {
204
+ * const { imageUrl, voucherCode } = await getVoucherDataForUser(event.user.id);
205
+ * return [
206
+ * {
207
+ * filename: `voucher-${voucherCode}.jpg`,
208
+ * path: imageUrl,
209
+ * },
210
+ * ];
211
+ * });
212
+ * ```
213
+ */
214
+ setAttachments(setAttachmentsFn: SetAttachmentsFn<Event>): this;
215
+ /**
216
+ * @description
217
+ * Add configuration for another template other than the default `"body.hbs"`. Use this method to define specific
218
+ * templates for channels or languageCodes other than the default.
219
+ *
220
+ * @deprecated Define a custom TemplateLoader on plugin initalization to define templates based on the RequestContext.
221
+ * E.g. `EmailPlugin.init({ templateLoader: new CustomTemplateLoader() })`
222
+ */
223
+ addTemplate(config: EmailTemplateConfig): EmailEventHandler<T, Event>;
224
+ /**
225
+ * @description
226
+ * Allows data to be loaded asynchronously which can then be used as template variables.
227
+ * The `loadDataFn` has access to the event, the TypeORM `Connection` object, and an
228
+ * `inject()` function which can be used to inject any of the providers exported
229
+ * by the {@link PluginCommonModule}. The return value of the `loadDataFn` will be
230
+ * added to the `event` as the `data` property.
231
+ *
232
+ * @example
233
+ * ```ts
234
+ * new EmailEventListener('order-confirmation')
235
+ * .on(OrderStateTransitionEvent)
236
+ * .filter(event => event.toState === 'PaymentSettled' && !!event.order.customer)
237
+ * .loadData(({ event, injector }) => {
238
+ * const orderService = injector.get(OrderService);
239
+ * return orderService.getOrderPayments(event.order.id);
240
+ * })
241
+ * .setTemplateVars(event => ({
242
+ * order: event.order,
243
+ * payments: event.data,
244
+ * }))
245
+ * // ...
246
+ * ```
247
+ */
248
+ loadData<R>(loadDataFn: LoadDataFn<Event, R>): EmailEventHandlerWithAsyncData<R, T, Event, EventWithAsyncData<Event, R>>;
249
+ /**
250
+ * @description
251
+ * Used internally by the EmailPlugin to handle incoming events.
252
+ *
253
+ * @internal
254
+ */
255
+ handle(event: Event, globals: {
256
+ [key: string]: any;
257
+ } | undefined, injector: Injector): Promise<IntermediateEmailDetails | undefined>;
258
+ /**
259
+ * @description
260
+ * Optionally define a mock Event which is used by the dev mode mailbox app for generating mock emails
261
+ * from this handler, which is useful when developing the email templates.
262
+ */
263
+ setMockEvent(event: Omit<Event, "ctx" | "data">): EmailEventHandler<T, Event>;
264
+ private getBestConfiguration;
265
+ }
266
+ /**
267
+ * @description
268
+ * Identical to the {@link EmailEventHandler} but with a `data` property added to the `event` based on the result
269
+ * of the `.loadData()` function.
270
+ *
271
+ * @docsCategory core plugins/EmailPlugin
272
+ */
273
+ export declare class EmailEventHandlerWithAsyncData<Data, T extends string = string, InputEvent extends EventWithContext = EventWithContext, Event extends EventWithAsyncData<InputEvent, Data> = EventWithAsyncData<InputEvent, Data>> extends EmailEventHandler<T, Event> {
274
+ _loadDataFn: LoadDataFn<InputEvent, Data>;
275
+ constructor(_loadDataFn: LoadDataFn<InputEvent, Data>, listener: EmailEventListener<T>, event: Type<InputEvent>);
276
+ }
@@ -0,0 +1,396 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EmailEventHandlerWithAsyncData = exports.EmailEventHandler = void 0;
4
+ const core_1 = require("@deenruv/core");
5
+ const attachment_utils_1 = require("../attachment-utils");
6
+ const constants_1 = require("../constants");
7
+ /**
8
+ * @description
9
+ * The EmailEventHandler defines how the EmailPlugin will respond to a given event.
10
+ *
11
+ * A handler is created by creating a new {@link EmailEventListener} and calling the `.on()` method
12
+ * to specify which event to respond to.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * const confirmationHandler = new EmailEventListener('order-confirmation')
17
+ * .on(OrderStateTransitionEvent)
18
+ * .filter(event => event.toState === 'PaymentSettled')
19
+ * .setRecipient(event => event.order.customer.emailAddress)
20
+ * .setFrom('{{ fromAddress }}')
21
+ * .setSubject(`Order confirmation for #{{ order.code }}`)
22
+ * .setTemplateVars(event => ({ order: event.order }));
23
+ * ```
24
+ *
25
+ * This example creates a handler which listens for the `OrderStateTransitionEvent` and if the Order has
26
+ * transitioned to the `'PaymentSettled'` state, it will generate and send an email.
27
+ *
28
+ * The string argument passed into the `EmailEventListener` constructor is used to identify the handler, and
29
+ * also to locate the directory of the email template files. So in the example above, there should be a directory
30
+ * `<app root>/static/email/templates/order-confirmation` which contains a Handlebars template named `body.hbs`.
31
+ *
32
+ * ## Handling other languages
33
+ *
34
+ * By default, the handler will respond to all events on all channels and use the same subject ("Order confirmation for #12345" above)
35
+ * and body template. Where the server is intended to support multiple languages, the `.addTemplate()` method may be used
36
+ * to define the subject and body template for specific language and channel combinations.
37
+ *
38
+ * The language is determined by looking at the `languageCode` property of the event's `ctx` ({@link RequestContext}) object.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * const extendedConfirmationHandler = confirmationHandler
43
+ * .addTemplate({
44
+ * channelCode: 'default',
45
+ * languageCode: LanguageCode.de,
46
+ * templateFile: 'body.de.hbs',
47
+ * subject: 'Bestellbestätigung für #{{ order.code }}',
48
+ * })
49
+ * ```
50
+ *
51
+ * ## Defining a custom handler
52
+ *
53
+ * Let's say you have a plugin which defines a new event type, `QuoteRequestedEvent`. In your plugin you have defined a mutation
54
+ * which is executed when the customer requests a quote in your storefront, and in your resolver, you use the {@link EventBus} to publish a
55
+ * new `QuoteRequestedEvent`.
56
+ *
57
+ * You now want to email the customer with their quote. Here are the steps you would take to set this up:
58
+ *
59
+ * ### 1. Create a new handler
60
+ *
61
+ * ```ts
62
+ * import { EmailEventListener } from `\@deenruv/email-plugin`;
63
+ * import { QuoteRequestedEvent } from `./events`;
64
+ *
65
+ * const quoteRequestedHandler = new EmailEventListener('quote-requested')
66
+ * .on(QuoteRequestedEvent)
67
+ * .setRecipient(event => event.customer.emailAddress)
68
+ * .setSubject(`Here's the quote you requested`)
69
+ * .setFrom('{{ fromAddress }}')
70
+ * .setTemplateVars(event => ({ details: event.details }));
71
+ * ```
72
+ *
73
+ * ### 2. Create the email template
74
+ *
75
+ * Next you need to make sure there is a template defined at `<app root>/static/email/templates/quote-requested/body.hbs`. The path
76
+ * segment `quote-requested` must match the string passed to the `EmailEventListener` constructor.
77
+ *
78
+ * The template would look something like this:
79
+ *
80
+ * ```handlebars
81
+ * {{> header title="Here's the quote you requested" }}
82
+ *
83
+ * <mj-section background-color="#fafafa">
84
+ * <mj-column>
85
+ * <mj-text color="#525252">
86
+ * Thank you for your interest in our products! Here's the details
87
+ * of the quote you recently requested:
88
+ * </mj-text>
89
+ *
90
+ * <--! your custom email layout goes here -->
91
+ * </mj-column>
92
+ * </mj-section>
93
+ *
94
+ *
95
+ * {{> footer }}
96
+ * ```
97
+ *
98
+ * You can find pre-made templates on the [MJML website](https://mjml.io/templates/).
99
+ *
100
+ * ### 3. Register the handler
101
+ *
102
+ * Finally, you need to register the handler with the EmailPlugin:
103
+ *
104
+ * ```ts {hl_lines=[8]}
105
+ * import { defaultEmailHandlers, EmailPlugin } from '\@deenruv/email-plugin';
106
+ * import { quoteRequestedHandler } from './plugins/quote-plugin';
107
+ *
108
+ * const config: DeenruvConfig = {
109
+ * // Add an instance of the plugin to the plugins array
110
+ * plugins: [
111
+ * EmailPlugin.init({
112
+ * handler: [...defaultEmailHandlers, quoteRequestedHandler],
113
+ * templatePath: path.join(__dirname, 'deenruv/email/templates'),
114
+ * // ... etc
115
+ * }),
116
+ * ],
117
+ * };
118
+ * ```
119
+ *
120
+ * @docsCategory core plugins/EmailPlugin
121
+ */
122
+ class EmailEventHandler {
123
+ constructor(listener, event) {
124
+ this.listener = listener;
125
+ this.event = event;
126
+ this.filterFns = [];
127
+ this.configurations = [];
128
+ }
129
+ /** @internal */
130
+ get type() {
131
+ return this.listener.type;
132
+ }
133
+ /** @internal */
134
+ get mockEvent() {
135
+ return this._mockEvent;
136
+ }
137
+ /**
138
+ * @description
139
+ * Defines a predicate function which is used to determine whether the event will trigger an email.
140
+ * Multiple filter functions may be defined.
141
+ */
142
+ filter(filterFn) {
143
+ this.filterFns.push(filterFn);
144
+ return this;
145
+ }
146
+ /**
147
+ * @description
148
+ * A function which defines how the recipient email address should be extracted from the incoming event.
149
+ *
150
+ * The recipient can be a plain email address: `'foobar@example.com'`
151
+ * Or with a formatted name (includes unicode support): `'Ноде Майлер <foobar@example.com>'`
152
+ * Or a comma-separated list of addresses: `'foobar@example.com, "Ноде Майлер" <bar@example.com>'`
153
+ */
154
+ setRecipient(setRecipientFn) {
155
+ this.setRecipientFn = setRecipientFn;
156
+ return this;
157
+ }
158
+ /**
159
+ * @description
160
+ * A function which allows to override the language of the email. If not defined, the language from the context will be used.
161
+ *
162
+ * @since 1.8.0
163
+ */
164
+ setLanguageCode(setLanguageCodeFn) {
165
+ this.setLanguageCodeFn = setLanguageCodeFn;
166
+ return this;
167
+ }
168
+ /**
169
+ * @description
170
+ * A function which returns an object hash of variables which will be made available to the Handlebars template
171
+ * and subject line for interpolation.
172
+ */
173
+ setTemplateVars(templateVarsFn) {
174
+ this.setTemplateVarsFn = templateVarsFn;
175
+ return this;
176
+ }
177
+ /**
178
+ * @description
179
+ * Sets the default subject of the email. The subject string may use Handlebars variables defined by the
180
+ * setTemplateVars() method.
181
+ */
182
+ setSubject(defaultSubject) {
183
+ if (typeof defaultSubject === "string") {
184
+ this.defaultSubject = defaultSubject;
185
+ }
186
+ else {
187
+ this.setSubjectFn = defaultSubject;
188
+ }
189
+ return this;
190
+ }
191
+ /**
192
+ * @description
193
+ * Sets the default from field of the email. The from string may use Handlebars variables defined by the
194
+ * setTemplateVars() method.
195
+ */
196
+ setFrom(from) {
197
+ this.from = from;
198
+ return this;
199
+ }
200
+ /**
201
+ * @description
202
+ * A function which allows {@link OptionalAddressFields} to be specified such as "cc" and "bcc".
203
+ *
204
+ * @since 1.1.0
205
+ */
206
+ setOptionalAddressFields(optionalAddressFieldsFn) {
207
+ this.setOptionalAddressFieldsFn = optionalAddressFieldsFn;
208
+ return this;
209
+ }
210
+ /**
211
+ * @description
212
+ * Defines one or more files to be attached to the email. An attachment can be specified
213
+ * as either a `path` (to a file or URL) or as `content` which can be a string, Buffer or Stream.
214
+ *
215
+ * **Note:** When using the `content` to pass a Buffer or Stream, the raw data will get serialized
216
+ * into the job queue. For this reason the total size of all attachments passed as `content` should kept to
217
+ * **less than ~50k**. If the attachments are greater than that limit, a warning will be logged and
218
+ * errors may result if using the DefaultJobQueuePlugin with certain DBs such as MySQL/MariaDB.
219
+ *
220
+ * @example
221
+ * ```ts
222
+ * const testAttachmentHandler = new EmailEventListener('activate-voucher')
223
+ * .on(ActivateVoucherEvent)
224
+ * // ... omitted some steps for brevity
225
+ * .setAttachments(async (event) => {
226
+ * const { imageUrl, voucherCode } = await getVoucherDataForUser(event.user.id);
227
+ * return [
228
+ * {
229
+ * filename: `voucher-${voucherCode}.jpg`,
230
+ * path: imageUrl,
231
+ * },
232
+ * ];
233
+ * });
234
+ * ```
235
+ */
236
+ setAttachments(setAttachmentsFn) {
237
+ this.setAttachmentsFn = setAttachmentsFn;
238
+ return this;
239
+ }
240
+ /**
241
+ * @description
242
+ * Add configuration for another template other than the default `"body.hbs"`. Use this method to define specific
243
+ * templates for channels or languageCodes other than the default.
244
+ *
245
+ * @deprecated Define a custom TemplateLoader on plugin initalization to define templates based on the RequestContext.
246
+ * E.g. `EmailPlugin.init({ templateLoader: new CustomTemplateLoader() })`
247
+ */
248
+ addTemplate(config) {
249
+ this.configurations.push(config);
250
+ return this;
251
+ }
252
+ /**
253
+ * @description
254
+ * Allows data to be loaded asynchronously which can then be used as template variables.
255
+ * The `loadDataFn` has access to the event, the TypeORM `Connection` object, and an
256
+ * `inject()` function which can be used to inject any of the providers exported
257
+ * by the {@link PluginCommonModule}. The return value of the `loadDataFn` will be
258
+ * added to the `event` as the `data` property.
259
+ *
260
+ * @example
261
+ * ```ts
262
+ * new EmailEventListener('order-confirmation')
263
+ * .on(OrderStateTransitionEvent)
264
+ * .filter(event => event.toState === 'PaymentSettled' && !!event.order.customer)
265
+ * .loadData(({ event, injector }) => {
266
+ * const orderService = injector.get(OrderService);
267
+ * return orderService.getOrderPayments(event.order.id);
268
+ * })
269
+ * .setTemplateVars(event => ({
270
+ * order: event.order,
271
+ * payments: event.data,
272
+ * }))
273
+ * // ...
274
+ * ```
275
+ */
276
+ loadData(loadDataFn) {
277
+ const asyncHandler = new EmailEventHandlerWithAsyncData(loadDataFn, this.listener, this.event);
278
+ asyncHandler.setRecipientFn = this.setRecipientFn;
279
+ asyncHandler.setTemplateVarsFn = this.setTemplateVarsFn;
280
+ asyncHandler.setAttachmentsFn = this.setAttachmentsFn;
281
+ asyncHandler.setOptionalAddressFieldsFn = this.setOptionalAddressFieldsFn;
282
+ asyncHandler.filterFns = this.filterFns;
283
+ asyncHandler.configurations = this.configurations;
284
+ asyncHandler.defaultSubject = this.defaultSubject;
285
+ asyncHandler.from = this.from;
286
+ asyncHandler._mockEvent = this._mockEvent;
287
+ return asyncHandler;
288
+ }
289
+ /**
290
+ * @description
291
+ * Used internally by the EmailPlugin to handle incoming events.
292
+ *
293
+ * @internal
294
+ */
295
+ async handle(event, globals = {}, injector) {
296
+ var _a, _b, _c, _d, _e;
297
+ for (const filterFn of this.filterFns) {
298
+ if (!filterFn(event)) {
299
+ return;
300
+ }
301
+ }
302
+ if (this instanceof EmailEventHandlerWithAsyncData) {
303
+ try {
304
+ event.data = await this._loadDataFn({
305
+ event,
306
+ injector,
307
+ });
308
+ }
309
+ catch (err) {
310
+ if (err instanceof Error) {
311
+ core_1.Logger.error(err.message, constants_1.loggerCtx, err.stack);
312
+ }
313
+ else {
314
+ core_1.Logger.error(String(err), constants_1.loggerCtx);
315
+ }
316
+ return;
317
+ }
318
+ }
319
+ if (!this.setRecipientFn) {
320
+ throw new Error(`No setRecipientFn has been defined. ` +
321
+ `Remember to call ".setRecipient()" when setting up the EmailEventHandler for ${this.type}`);
322
+ }
323
+ if (this.from === undefined) {
324
+ throw new Error(`No from field has been defined. ` +
325
+ `Remember to call ".setFrom()" when setting up the EmailEventHandler for ${this.type}`);
326
+ }
327
+ const { ctx } = event;
328
+ const languageCode = ((_a = this.setLanguageCodeFn) === null || _a === void 0 ? void 0 : _a.call(this, event)) || ctx.languageCode;
329
+ const configuration = this.getBestConfiguration(ctx.channel.code, languageCode);
330
+ const subject = configuration
331
+ ? configuration.subject
332
+ : this.setSubjectFn
333
+ ? await this.setSubjectFn(event, ctx, injector)
334
+ : this.defaultSubject;
335
+ if (subject == null) {
336
+ throw new Error(`No subject field has been defined. ` +
337
+ `Remember to call ".setSubject()" when setting up the EmailEventHandler for ${this.type}`);
338
+ }
339
+ const recipient = this.setRecipientFn(event);
340
+ const templateVars = this.setTemplateVarsFn
341
+ ? this.setTemplateVarsFn(event, globals)
342
+ : {};
343
+ let attachmentsArray = [];
344
+ try {
345
+ attachmentsArray = (_c = (await ((_b = this.setAttachmentsFn) === null || _b === void 0 ? void 0 : _b.call(this, event)))) !== null && _c !== void 0 ? _c : [];
346
+ }
347
+ catch (e) {
348
+ core_1.Logger.error(e, constants_1.loggerCtx, e.stack);
349
+ }
350
+ const attachments = await (0, attachment_utils_1.serializeAttachments)(attachmentsArray);
351
+ const optionalAddressFields = (_e = (await ((_d = this.setOptionalAddressFieldsFn) === null || _d === void 0 ? void 0 : _d.call(this, event)))) !== null && _e !== void 0 ? _e : {};
352
+ return Object.assign({ ctx: event.ctx.serialize(), type: this.type, recipient, from: this.from, templateVars: Object.assign(Object.assign({}, globals), templateVars), subject, templateFile: configuration ? configuration.templateFile : "body.hbs", attachments }, optionalAddressFields);
353
+ }
354
+ /**
355
+ * @description
356
+ * Optionally define a mock Event which is used by the dev mode mailbox app for generating mock emails
357
+ * from this handler, which is useful when developing the email templates.
358
+ */
359
+ setMockEvent(event) {
360
+ this._mockEvent = event;
361
+ return this;
362
+ }
363
+ getBestConfiguration(channelCode, languageCode) {
364
+ if (this.configurations.length === 0) {
365
+ return;
366
+ }
367
+ const exactMatch = this.configurations.find((c) => {
368
+ return ((c.channelCode === channelCode || c.channelCode === "default") &&
369
+ c.languageCode === languageCode);
370
+ });
371
+ if (exactMatch) {
372
+ return exactMatch;
373
+ }
374
+ const channelMatch = this.configurations.find((c) => c.channelCode === channelCode && c.languageCode === "default");
375
+ if (channelMatch) {
376
+ return channelMatch;
377
+ }
378
+ return;
379
+ }
380
+ }
381
+ exports.EmailEventHandler = EmailEventHandler;
382
+ /**
383
+ * @description
384
+ * Identical to the {@link EmailEventHandler} but with a `data` property added to the `event` based on the result
385
+ * of the `.loadData()` function.
386
+ *
387
+ * @docsCategory core plugins/EmailPlugin
388
+ */
389
+ class EmailEventHandlerWithAsyncData extends EmailEventHandler {
390
+ constructor(_loadDataFn, listener, event) {
391
+ super(listener, event);
392
+ this._loadDataFn = _loadDataFn;
393
+ }
394
+ }
395
+ exports.EmailEventHandlerWithAsyncData = EmailEventHandlerWithAsyncData;
396
+ //# sourceMappingURL=event-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-handler.js","sourceRoot":"","sources":["../../../src/handler/event-handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import { AccountRegistrationEvent, IdentifierChangeRequestEvent, OrderStateTransitionEvent, PasswordResetEvent } from "@deenruv/core";
2
+ export declare const mockOrderStateTransitionEvent: OrderStateTransitionEvent;
3
+ export declare const mockAccountRegistrationEvent: AccountRegistrationEvent;
4
+ export declare const mockPasswordResetEvent: PasswordResetEvent;
5
+ export declare const mockEmailAddressChangeEvent: IdentifierChangeRequestEvent;