@nocobase/plugin-workflow-mailer 2.1.0-beta.9 → 2.2.0-alpha.1

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 (44) hide show
  1. package/dist/client/index.js +1 -1
  2. package/dist/externalVersion.js +4 -4
  3. package/dist/node_modules/joi/dist/joi-browser.min.js +1 -0
  4. package/dist/node_modules/joi/lib/annotate.js +175 -0
  5. package/dist/node_modules/joi/lib/base.js +1069 -0
  6. package/dist/node_modules/joi/lib/cache.js +143 -0
  7. package/dist/node_modules/joi/lib/common.js +216 -0
  8. package/dist/node_modules/joi/lib/compile.js +283 -0
  9. package/dist/node_modules/joi/lib/errors.js +271 -0
  10. package/dist/node_modules/joi/lib/extend.js +312 -0
  11. package/dist/node_modules/joi/lib/index.d.ts +2365 -0
  12. package/dist/node_modules/joi/lib/index.js +1 -0
  13. package/dist/node_modules/joi/lib/manifest.js +476 -0
  14. package/dist/node_modules/joi/lib/messages.js +178 -0
  15. package/dist/node_modules/joi/lib/modify.js +267 -0
  16. package/dist/node_modules/joi/lib/ref.js +414 -0
  17. package/dist/node_modules/joi/lib/schemas.js +302 -0
  18. package/dist/node_modules/joi/lib/state.js +166 -0
  19. package/dist/node_modules/joi/lib/template.js +463 -0
  20. package/dist/node_modules/joi/lib/trace.js +346 -0
  21. package/dist/node_modules/joi/lib/types/alternatives.js +364 -0
  22. package/dist/node_modules/joi/lib/types/any.js +174 -0
  23. package/dist/node_modules/joi/lib/types/array.js +809 -0
  24. package/dist/node_modules/joi/lib/types/binary.js +100 -0
  25. package/dist/node_modules/joi/lib/types/boolean.js +150 -0
  26. package/dist/node_modules/joi/lib/types/date.js +233 -0
  27. package/dist/node_modules/joi/lib/types/function.js +93 -0
  28. package/dist/node_modules/joi/lib/types/keys.js +1067 -0
  29. package/dist/node_modules/joi/lib/types/link.js +168 -0
  30. package/dist/node_modules/joi/lib/types/number.js +363 -0
  31. package/dist/node_modules/joi/lib/types/object.js +22 -0
  32. package/dist/node_modules/joi/lib/types/string.js +850 -0
  33. package/dist/node_modules/joi/lib/types/symbol.js +102 -0
  34. package/dist/node_modules/joi/lib/validator.js +750 -0
  35. package/dist/node_modules/joi/lib/values.js +263 -0
  36. package/dist/node_modules/joi/node_modules/@hapi/topo/lib/index.d.ts +60 -0
  37. package/dist/node_modules/joi/node_modules/@hapi/topo/lib/index.js +225 -0
  38. package/dist/node_modules/joi/node_modules/@hapi/topo/package.json +30 -0
  39. package/dist/node_modules/joi/package.json +1 -0
  40. package/dist/node_modules/nodemailer/lib/nodemailer.js +1 -1
  41. package/dist/node_modules/nodemailer/package.json +1 -1
  42. package/dist/server/MailerInstruction.d.ts +7 -12
  43. package/dist/server/MailerInstruction.js +171 -41
  44. package/package.json +3 -2
@@ -1 +1 @@
1
- {"name":"nodemailer","version":"6.9.13","description":"Easy as cake e-mail sending from your Node.js applications","main":"lib/nodemailer.js","scripts":{"test":"node --test --test-concurrency=1 test/**/*.test.js test/**/*-test.js","test:coverage":"c8 node --test --test-concurrency=1 test/**/*.test.js test/**/*-test.js","lint":"eslint .","update":"rm -rf node_modules/ package-lock.json && ncu -u && npm install"},"repository":{"type":"git","url":"https://github.com/nodemailer/nodemailer.git"},"keywords":["Nodemailer"],"author":"Andris Reinman","license":"MIT-0","bugs":{"url":"https://github.com/nodemailer/nodemailer/issues"},"homepage":"https://nodemailer.com/","devDependencies":{"@aws-sdk/client-ses":"3.529.1","bunyan":"1.8.15","c8":"9.1.0","eslint":"8.57.0","eslint-config-nodemailer":"1.2.0","eslint-config-prettier":"9.1.0","libbase64":"1.3.0","libmime":"5.3.4","libqp":"2.1.0","nodemailer-ntlm-auth":"1.0.4","proxy":"1.0.2","proxy-test-server":"1.0.0","smtp-server":"3.13.3"},"engines":{"node":">=6.0.0"},"_lastModified":"2026-03-12T13:34:31.163Z"}
1
+ {"name":"nodemailer","version":"6.9.13","description":"Easy as cake e-mail sending from your Node.js applications","main":"lib/nodemailer.js","scripts":{"test":"node --test --test-concurrency=1 test/**/*.test.js test/**/*-test.js","test:coverage":"c8 node --test --test-concurrency=1 test/**/*.test.js test/**/*-test.js","lint":"eslint .","update":"rm -rf node_modules/ package-lock.json && ncu -u && npm install"},"repository":{"type":"git","url":"https://github.com/nodemailer/nodemailer.git"},"keywords":["Nodemailer"],"author":"Andris Reinman","license":"MIT-0","bugs":{"url":"https://github.com/nodemailer/nodemailer/issues"},"homepage":"https://nodemailer.com/","devDependencies":{"@aws-sdk/client-ses":"3.529.1","bunyan":"1.8.15","c8":"9.1.0","eslint":"8.57.0","eslint-config-nodemailer":"1.2.0","eslint-config-prettier":"9.1.0","libbase64":"1.3.0","libmime":"5.3.4","libqp":"2.1.0","nodemailer-ntlm-auth":"1.0.4","proxy":"1.0.2","proxy-test-server":"1.0.0","smtp-server":"3.13.3"},"engines":{"node":">=6.0.0"},"_lastModified":"2026-06-10T18:13:29.425Z"}
@@ -6,17 +6,12 @@
6
6
  * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
- import { FlowNodeModel, Instruction, Processor } from '@nocobase/plugin-workflow';
9
+ import Joi from 'joi';
10
+ import { FlowNodeModel, Instruction, InstructionResult, JobModel, Processor } from '@nocobase/plugin-workflow';
10
11
  export default class MailerInstruction extends Instruction {
11
- private static transporterMap;
12
- private static configMap;
13
- private getTransporterKey;
14
- private isConfigChanged;
15
- private createNewTransporter;
16
- private getTransporter;
17
- run(node: FlowNodeModel, prevJob: any, processor: Processor): Promise<{
18
- status: 1 | -1;
19
- result: any;
20
- }>;
21
- resume(node: FlowNodeModel, job: any, processor: Processor): Promise<any>;
12
+ configSchema: Joi.ObjectSchema<any>;
13
+ run(node: FlowNodeModel, prevJob: JobModel, processor: Processor, options?: {
14
+ signal?: AbortSignal;
15
+ }): Promise<InstructionResult>;
16
+ resume(node: FlowNodeModel, job: JobModel, processor: Processor): Promise<JobModel>;
22
17
  }
@@ -39,44 +39,151 @@ __export(MailerInstruction_exports, {
39
39
  default: () => MailerInstruction
40
40
  });
41
41
  module.exports = __toCommonJS(MailerInstruction_exports);
42
- var import_util = require("util");
42
+ var import_joi = __toESM(require("joi"));
43
43
  var import_nodemailer = __toESM(require("nodemailer"));
44
44
  var import_plugin_workflow = require("@nocobase/plugin-workflow");
45
45
  var import_get = __toESM(require("lodash/get"));
46
- class MailerInstruction extends import_plugin_workflow.Instruction {
47
- static transporterMap = /* @__PURE__ */ new Map();
48
- static configMap = /* @__PURE__ */ new Map();
49
- getTransporterKey(provider) {
50
- const { host, port, auth } = provider;
51
- return `${host}:${port}:${auth == null ? void 0 : auth.user}`;
46
+ const transporterMap = /* @__PURE__ */ new Map();
47
+ const configMap = /* @__PURE__ */ new Map();
48
+ function getTransporterKey(provider) {
49
+ const { host, port, auth } = provider;
50
+ return `${host}:${port}:${auth == null ? void 0 : auth.user}`;
51
+ }
52
+ function isConfigChanged(oldConfig, newConfig) {
53
+ const fields = ["host", "port", "secure", "auth.user", "auth.pass"];
54
+ return fields.some((key) => (0, import_get.default)(oldConfig, key) !== (0, import_get.default)(newConfig, key));
55
+ }
56
+ function createNewTransporter(key, config) {
57
+ const transporter = import_nodemailer.default.createTransport(config);
58
+ transporterMap.set(key, transporter);
59
+ configMap.set(key, config);
60
+ return transporter;
61
+ }
62
+ function getTransporter(provider) {
63
+ const key = getTransporterKey(provider);
64
+ const newConfig = provider;
65
+ const oldConfig = configMap.get(key);
66
+ if (!oldConfig) {
67
+ return createNewTransporter(key, newConfig);
52
68
  }
53
- isConfigChanged(oldConfig, newConfig) {
54
- const fields = ["host", "port", "secure", "auth.user", "auth.pass"];
55
- return fields.some((key) => (0, import_get.default)(oldConfig, key) !== (0, import_get.default)(newConfig, key));
69
+ if (isConfigChanged(oldConfig, newConfig)) {
70
+ const oldTransporter = transporterMap.get(key);
71
+ if (oldTransporter) {
72
+ oldTransporter.close();
73
+ }
74
+ return createNewTransporter(key, newConfig);
56
75
  }
57
- createNewTransporter(key, config) {
58
- const transporter = import_nodemailer.default.createTransport(config);
59
- MailerInstruction.transporterMap.set(key, transporter);
60
- MailerInstruction.configMap.set(key, config);
61
- return transporter;
76
+ return transporterMap.get(key);
77
+ }
78
+ function discardTransporter(key, transporter) {
79
+ transporter.close();
80
+ if (transporterMap.get(key) === transporter) {
81
+ transporterMap.delete(key);
82
+ configMap.delete(key);
62
83
  }
63
- getTransporter(provider) {
64
- const key = this.getTransporterKey(provider);
65
- const newConfig = provider;
66
- const oldConfig = MailerInstruction.configMap.get(key);
67
- if (!oldConfig) {
68
- return this.createNewTransporter(key, newConfig);
84
+ }
85
+ function createAbortSignal(processor, signal, timeout = 0) {
86
+ var _a, _b, _c, _d, _e, _f, _g;
87
+ const abortController = new AbortController();
88
+ let timeoutTimer = null;
89
+ let abortListener = null;
90
+ const abort = (reason) => {
91
+ if (abortController.signal.aborted) {
92
+ return;
69
93
  }
70
- if (this.isConfigChanged(oldConfig, newConfig)) {
71
- const oldTransporter = MailerInstruction.transporterMap.get(key);
72
- if (oldTransporter) {
73
- oldTransporter.close();
94
+ abortController.abort(reason instanceof Error ? reason : new import_plugin_workflow.WorkflowTimeoutError());
95
+ };
96
+ if (signal == null ? void 0 : signal.aborted) {
97
+ abort(signal.reason);
98
+ } else {
99
+ abortListener = () => abort(signal == null ? void 0 : signal.reason);
100
+ signal == null ? void 0 : signal.addEventListener("abort", abortListener, { once: true });
101
+ }
102
+ const executionWorkflow = processor.execution.workflow ?? ((_b = (_a = processor.execution).get) == null ? void 0 : _b.call(_a, "workflow"));
103
+ const executionWorkflowOptions = (executionWorkflow == null ? void 0 : executionWorkflow.options) ?? ((_c = executionWorkflow == null ? void 0 : executionWorkflow.get) == null ? void 0 : _c.call(executionWorkflow, "options")) ?? {};
104
+ const workflowTimeout = Number(timeout || executionWorkflowOptions.timeout || 0);
105
+ const expiresAt = processor.execution.expiresAt ?? ((_e = (_d = processor.execution).get) == null ? void 0 : _e.call(_d, "expiresAt"));
106
+ const startedAt = processor.execution.startedAt ?? ((_g = (_f = processor.execution).get) == null ? void 0 : _g.call(_f, "startedAt"));
107
+ const remaining = expiresAt ? new Date(expiresAt).getTime() - Date.now() : workflowTimeout > 0 && startedAt ? new Date(startedAt).getTime() + workflowTimeout - Date.now() : workflowTimeout > 0 ? workflowTimeout : null;
108
+ if (remaining != null) {
109
+ if (remaining <= 0) {
110
+ abort(new import_plugin_workflow.WorkflowTimeoutError());
111
+ } else {
112
+ timeoutTimer = setTimeout(() => abort(new import_plugin_workflow.WorkflowTimeoutError()), remaining);
113
+ }
114
+ }
115
+ return {
116
+ signal: abortController.signal,
117
+ cleanup: () => {
118
+ if (timeoutTimer) {
119
+ clearTimeout(timeoutTimer);
120
+ }
121
+ if (signal && abortListener) {
122
+ signal.removeEventListener("abort", abortListener);
74
123
  }
75
- return this.createNewTransporter(key, newConfig);
76
124
  }
77
- return MailerInstruction.transporterMap.get(key);
125
+ };
126
+ }
127
+ function sendMail(transporter, transporterKey, payload, signal) {
128
+ if (signal == null ? void 0 : signal.aborted) {
129
+ discardTransporter(transporterKey, transporter);
130
+ return Promise.reject(signal.reason);
78
131
  }
79
- async run(node, prevJob, processor) {
132
+ return new Promise((resolve, reject) => {
133
+ let finished = false;
134
+ const cleanup = () => {
135
+ signal == null ? void 0 : signal.removeEventListener("abort", onAbort);
136
+ };
137
+ const done = (error, result) => {
138
+ if (finished) {
139
+ return;
140
+ }
141
+ finished = true;
142
+ cleanup();
143
+ if (error) {
144
+ reject(error);
145
+ } else {
146
+ resolve(result);
147
+ }
148
+ };
149
+ const onAbort = () => {
150
+ if (finished) {
151
+ return;
152
+ }
153
+ discardTransporter(transporterKey, transporter);
154
+ done((signal == null ? void 0 : signal.reason) instanceof Error ? signal.reason : new import_plugin_workflow.WorkflowTimeoutError());
155
+ };
156
+ signal == null ? void 0 : signal.addEventListener("abort", onAbort, { once: true });
157
+ try {
158
+ transporter.sendMail(payload, done);
159
+ } catch (error) {
160
+ done(error);
161
+ }
162
+ });
163
+ }
164
+ class MailerInstruction extends import_plugin_workflow.Instruction {
165
+ configSchema = import_joi.default.object({
166
+ provider: import_joi.default.object({
167
+ host: import_joi.default.string(),
168
+ port: import_joi.default.alternatives().try(import_joi.default.number().port(), import_joi.default.string()).default(465),
169
+ secure: import_joi.default.alternatives().try(import_joi.default.boolean(), import_joi.default.string()).default(true),
170
+ auth: import_joi.default.object({
171
+ user: import_joi.default.string(),
172
+ pass: import_joi.default.string()
173
+ })
174
+ }),
175
+ from: import_joi.default.string(),
176
+ to: import_joi.default.array().items(import_joi.default.string()),
177
+ cc: import_joi.default.array().items(import_joi.default.string()),
178
+ bcc: import_joi.default.array().items(import_joi.default.string()),
179
+ subject: import_joi.default.string(),
180
+ contentType: import_joi.default.string().valid("html", "text").default("html"),
181
+ html: import_joi.default.string(),
182
+ text: import_joi.default.string(),
183
+ ignoreFail: import_joi.default.boolean().default(false)
184
+ });
185
+ async run(node, prevJob, processor, options) {
186
+ var _a, _b;
80
187
  const {
81
188
  provider,
82
189
  contentType,
@@ -87,32 +194,41 @@ class MailerInstruction extends import_plugin_workflow.Instruction {
87
194
  html,
88
195
  text,
89
196
  ignoreFail,
90
- ...options
197
+ ...others
91
198
  } = processor.getParsedValue(node.config, node.id);
92
199
  const { workflow } = processor.execution;
93
- const sync = this.workflow.isWorkflowSync(workflow);
94
- const transporter = this.getTransporter(provider);
95
- const send = (0, import_util.promisify)(transporter.sendMail.bind(transporter));
200
+ const currentWorkflow = (workflow == null ? void 0 : workflow.options) || ((_a = workflow == null ? void 0 : workflow.get) == null ? void 0 : _a.call(workflow, "options")) ? workflow : await node.getWorkflow();
201
+ const sync = this.workflow.isWorkflowSync(currentWorkflow);
202
+ const workflowOptions = (currentWorkflow == null ? void 0 : currentWorkflow.options) ?? ((_b = currentWorkflow == null ? void 0 : currentWorkflow.get) == null ? void 0 : _b.call(currentWorkflow, "options")) ?? {};
203
+ const workflowTimeout = Number(workflowOptions.timeout ?? 0);
204
+ const transporterKey = getTransporterKey(provider);
205
+ const transporter = getTransporter(provider);
206
+ const mailAbort = createAbortSignal(processor, options == null ? void 0 : options.signal, workflowTimeout);
96
207
  const payload = {
97
- ...options,
208
+ ...others,
98
209
  ...contentType === "html" ? { html } : { text },
99
210
  subject: subject == null ? void 0 : subject.trim(),
100
211
  to: to ? to.flat().map((item) => item == null ? void 0 : item.trim()).filter(Boolean) : void 0,
101
- cc: cc ? cc.flat().map((item) => item == null ? void 0 : item.trim()).filter(Boolean) : null,
102
- bcc: bcc ? bcc.flat().map((item) => item == null ? void 0 : item.trim()).filter(Boolean) : null
212
+ cc: cc ? cc.flat().map((item) => item == null ? void 0 : item.trim()).filter(Boolean) : [],
213
+ bcc: bcc ? bcc.flat().map((item) => item == null ? void 0 : item.trim()).filter(Boolean) : []
103
214
  };
104
215
  if (sync) {
105
216
  try {
106
- const result = await send(payload);
217
+ const result = await sendMail(transporter, transporterKey, payload, mailAbort.signal);
107
218
  return {
108
219
  status: import_plugin_workflow.JOB_STATUS.RESOLVED,
109
220
  result
110
221
  };
111
222
  } catch (error) {
223
+ if (mailAbort.signal.aborted) {
224
+ throw error;
225
+ }
112
226
  return {
113
227
  status: ignoreFail ? import_plugin_workflow.JOB_STATUS.RESOLVED : import_plugin_workflow.JOB_STATUS.FAILED,
114
228
  result: error
115
229
  };
230
+ } finally {
231
+ mailAbort.cleanup();
116
232
  }
117
233
  }
118
234
  const { id } = processor.saveJob({
@@ -124,26 +240,40 @@ class MailerInstruction extends import_plugin_workflow.Instruction {
124
240
  await processor.exit();
125
241
  const jobDone = { status: import_plugin_workflow.JOB_STATUS.PENDING };
126
242
  try {
127
- const response = await send(payload);
243
+ const response = await sendMail(transporter, transporterKey, payload, mailAbort.signal);
128
244
  processor.logger.info(`smtp-mailer (#${node.id}) sent successfully.`);
129
245
  jobDone.status = import_plugin_workflow.JOB_STATUS.RESOLVED;
130
246
  jobDone.result = response;
131
247
  } catch (error) {
132
248
  processor.logger.warn(`smtp-mailer (#${node.id}) sent failed: ${error.message}`);
133
- jobDone.status = import_plugin_workflow.JOB_STATUS.FAILED;
249
+ jobDone.status = mailAbort.signal.aborted ? import_plugin_workflow.JOB_STATUS.ABORTED : import_plugin_workflow.JOB_STATUS.FAILED;
134
250
  jobDone.result = error;
135
251
  } finally {
252
+ mailAbort.cleanup();
136
253
  processor.logger.debug(`smtp-mailer (#${node.id}) sending ended, resume workflow...`);
137
254
  const job = await this.workflow.app.db.getRepository("jobs").findOne({
138
255
  filterByTk: id
139
256
  });
140
- job.set(jobDone);
141
- this.workflow.resume(job);
257
+ const execution = await job.getExecution();
258
+ const aborted = await this.workflow.abortExecutionIfExpired(execution);
259
+ if (!aborted) {
260
+ await execution.reload();
261
+ await job.reload();
262
+ }
263
+ if (!aborted && execution.status === import_plugin_workflow.EXECUTION_STATUS.STARTED && job.status === import_plugin_workflow.JOB_STATUS.PENDING) {
264
+ job.set(jobDone);
265
+ job.execution = execution;
266
+ this.workflow.resume(job);
267
+ } else {
268
+ processor.logger.warn(
269
+ `smtp-mailer (#${node.id}) result discarded because execution (${execution.id}) is ended`
270
+ );
271
+ }
142
272
  }
143
273
  }
144
274
  async resume(node, job, processor) {
145
275
  const { ignoreFail } = node.config;
146
- if (ignoreFail) {
276
+ if (ignoreFail && job.status !== import_plugin_workflow.JOB_STATUS.ABORTED) {
147
277
  job.set("status", import_plugin_workflow.JOB_STATUS.RESOLVED);
148
278
  }
149
279
  return job;
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "description": "Send email in workflow.",
7
7
  "description.ru-RU": "Отправляет электронное письмо в рамках рабочего процесса.",
8
8
  "description.zh-CN": "可用于在工作流中发送电子邮件。",
9
- "version": "2.1.0-beta.9",
9
+ "version": "2.2.0-alpha.1",
10
10
  "license": "Apache-2.0",
11
11
  "main": "./dist/server/index.js",
12
12
  "homepage": "https://docs.nocobase.com/handbook/workflow-smtp-mailer",
@@ -14,6 +14,7 @@
14
14
  "homepage.ru-RU": "https://docs-ru.nocobase.com/handbook/workflow-smtp-mailer",
15
15
  "devDependencies": {
16
16
  "antd": "5.x",
17
+ "joi": "^17.13.3",
17
18
  "nodemailer": "6.9.13",
18
19
  "react": "18.x"
19
20
  },
@@ -24,7 +25,7 @@
24
25
  "@nocobase/server": "2.x",
25
26
  "@nocobase/test": "2.x"
26
27
  },
27
- "gitHead": "c3a2875e4cbbb43b1f2361e6f9f5f84a7d3f3c3c",
28
+ "gitHead": "303663aba6c6eefa27e6a6435b4c0352074ec40f",
28
29
  "keywords": [
29
30
  "NocoBase",
30
31
  "Workflow",