@fsai-flow/core 0.0.5 → 0.1.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 (92) hide show
  1. package/dist/index.d.ts +17 -0
  2. package/dist/index.js +61 -0
  3. package/dist/lib/ActiveWebhooks.d.ts +59 -0
  4. package/dist/lib/ActiveWebhooks.js +177 -0
  5. package/dist/lib/ActiveWorkflows.d.ts +87 -0
  6. package/dist/lib/ActiveWorkflows.js +465 -0
  7. package/dist/lib/BinaryDataManager/FileSystem.d.ts +26 -0
  8. package/dist/lib/BinaryDataManager/FileSystem.js +180 -0
  9. package/dist/lib/BinaryDataManager/index.d.ts +21 -0
  10. package/dist/lib/BinaryDataManager/index.js +129 -0
  11. package/dist/lib/ChangeCase.d.ts +9 -0
  12. package/dist/lib/ChangeCase.js +43 -0
  13. package/dist/lib/Constants.d.ts +14 -0
  14. package/dist/lib/Constants.js +18 -0
  15. package/dist/lib/Credentials.d.ts +27 -0
  16. package/dist/lib/Credentials.js +88 -0
  17. package/dist/lib/FileSystem.d.ts +26 -0
  18. package/dist/lib/FileSystem.js +180 -0
  19. package/dist/lib/InputConnectionDataLegacy.d.ts +2 -0
  20. package/dist/lib/InputConnectionDataLegacy.js +72 -0
  21. package/dist/lib/Interfaces.d.ts +147 -0
  22. package/dist/lib/Interfaces.js +2 -0
  23. package/dist/lib/LoadNodeParameterOptions.d.ts +39 -0
  24. package/dist/lib/LoadNodeParameterOptions.js +152 -0
  25. package/dist/lib/NodeExecuteFunctions.d.ts +225 -0
  26. package/dist/lib/NodeExecuteFunctions.js +2467 -0
  27. package/dist/lib/NodesLoader/constants.d.ts +5 -0
  28. package/dist/lib/NodesLoader/constants.js +105 -0
  29. package/dist/lib/NodesLoader/custom-directory-loader.d.ts +9 -0
  30. package/dist/lib/NodesLoader/custom-directory-loader.js +35 -0
  31. package/dist/lib/NodesLoader/directory-loader.d.ts +66 -0
  32. package/dist/lib/NodesLoader/directory-loader.js +367 -0
  33. package/dist/lib/NodesLoader/index.d.ts +5 -0
  34. package/dist/lib/NodesLoader/index.js +11 -0
  35. package/dist/lib/NodesLoader/lazy-package-directory-loader.d.ts +7 -0
  36. package/dist/lib/NodesLoader/lazy-package-directory-loader.js +44 -0
  37. package/dist/lib/NodesLoader/load-class-in-isolation.d.ts +1 -0
  38. package/dist/lib/NodesLoader/load-class-in-isolation.js +17 -0
  39. package/dist/lib/NodesLoader/package-directory-loader.d.ts +17 -0
  40. package/dist/lib/NodesLoader/package-directory-loader.js +92 -0
  41. package/dist/lib/NodesLoader/types.d.ts +14 -0
  42. package/dist/lib/NodesLoader/types.js +2 -0
  43. package/dist/lib/RedisLeaderElectionManager.d.ts +53 -0
  44. package/dist/lib/RedisLeaderElectionManager.js +279 -0
  45. package/dist/lib/RequestTypes.d.ts +58 -0
  46. package/dist/lib/RequestTypes.js +8 -0
  47. package/dist/lib/UserSettings.d.ts +80 -0
  48. package/dist/lib/UserSettings.js +269 -0
  49. package/dist/lib/WorkflowExecute.d.ts +53 -0
  50. package/dist/lib/WorkflowExecute.js +906 -0
  51. package/dist/lib/index.d.ts +21 -0
  52. package/dist/lib/index.js +129 -0
  53. package/dist/utils/crypto.d.ts +1 -0
  54. package/dist/utils/crypto.js +7 -0
  55. package/package.json +52 -52
  56. package/dist/README.md +0 -31
  57. package/dist/package.json +0 -54
  58. package/eslint.config.js +0 -19
  59. package/jest.config.ts +0 -10
  60. package/project.json +0 -19
  61. package/src/index.ts +0 -28
  62. package/src/lib/ActiveWebhooks.ts +0 -245
  63. package/src/lib/ActiveWorkflows.ts +0 -575
  64. package/src/lib/BinaryDataManager/FileSystem.ts +0 -214
  65. package/src/lib/BinaryDataManager/index.ts +0 -187
  66. package/src/lib/ChangeCase.ts +0 -45
  67. package/src/lib/Constants.ts +0 -16
  68. package/src/lib/Credentials.ts +0 -108
  69. package/src/lib/FileSystem.ts +0 -214
  70. package/src/lib/InputConnectionDataLegacy.ts +0 -123
  71. package/src/lib/Interfaces.ts +0 -338
  72. package/src/lib/LoadNodeParameterOptions.ts +0 -235
  73. package/src/lib/NodeExecuteFunctions.ts +0 -3700
  74. package/src/lib/NodesLoader/constants.ts +0 -112
  75. package/src/lib/NodesLoader/custom-directory-loader.ts +0 -31
  76. package/src/lib/NodesLoader/directory-loader.ts +0 -458
  77. package/src/lib/NodesLoader/index.ts +0 -5
  78. package/src/lib/NodesLoader/lazy-package-directory-loader.ts +0 -55
  79. package/src/lib/NodesLoader/load-class-in-isolation.ts +0 -19
  80. package/src/lib/NodesLoader/package-directory-loader.ts +0 -107
  81. package/src/lib/NodesLoader/types.ts +0 -14
  82. package/src/lib/RedisLeaderElectionManager.ts +0 -334
  83. package/src/lib/UserSettings.ts +0 -292
  84. package/src/lib/WorkflowExecute.ts +0 -1128
  85. package/src/lib/index.ts +0 -187
  86. package/src/utils/crypto.ts +0 -5
  87. package/tests/Credentials.test.ts +0 -88
  88. package/tests/Helpers.ts +0 -808
  89. package/tests/WorkflowExecute.test.ts +0 -1242
  90. package/tsconfig.json +0 -41
  91. package/tsconfig.lib.json +0 -10
  92. package/tsconfig.spec.json +0 -14
@@ -0,0 +1,2467 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.getBinaryDataBuffer = getBinaryDataBuffer;
40
+ exports.prepareBinaryData = prepareBinaryData;
41
+ exports.getCurrentOAuth2AccessToken = getCurrentOAuth2AccessToken;
42
+ exports.requestOAuth2 = requestOAuth2;
43
+ exports.requestOAuth1 = requestOAuth1;
44
+ exports.httpRequestWithAuthentication = httpRequestWithAuthentication;
45
+ exports.returnJsonArray = returnJsonArray;
46
+ exports.normalizeItems = normalizeItems;
47
+ exports.requestWithAuthentication = requestWithAuthentication;
48
+ exports.getAdditionalKeys = getAdditionalKeys;
49
+ exports.getCredentials = getCredentials;
50
+ exports.getNode = getNode;
51
+ exports.getNodeParameter = getNodeParameter;
52
+ exports.continueOnFail = continueOnFail;
53
+ exports.getNodeWebhookUrl = getNodeWebhookUrl;
54
+ exports.getTimezone = getTimezone;
55
+ exports.getWebhookDescription = getWebhookDescription;
56
+ exports.getWorkflowMetadata = getWorkflowMetadata;
57
+ exports.getExecutePollFunctions = getExecutePollFunctions;
58
+ exports.getExecuteTriggerFunctions = getExecuteTriggerFunctions;
59
+ exports.getExecuteFunctions = getExecuteFunctions;
60
+ exports.getExecuteSingleFunctions = getExecuteSingleFunctions;
61
+ exports.getCredentialTestFunctions = getCredentialTestFunctions;
62
+ exports.getLoadOptionsFunctions = getLoadOptionsFunctions;
63
+ exports.getExecuteHookFunctions = getExecuteHookFunctions;
64
+ exports.getExecuteWebhookFunctions = getExecuteWebhookFunctions;
65
+ exports.getSupplyDataFunctions = getSupplyDataFunctions;
66
+ exports.getInputConnectionData = getInputConnectionData;
67
+ const crypto = __importStar(require("node:crypto"));
68
+ const node_crypto_1 = require("node:crypto");
69
+ const node_fs_1 = require("node:fs");
70
+ const node_https_1 = require("node:https");
71
+ const path = __importStar(require("node:path"));
72
+ const url = __importStar(require("node:url"));
73
+ const node_url_1 = require("node:url");
74
+ const workflow_1 = require("@fsai-flow/workflow");
75
+ const axios_1 = __importDefault(require("axios"));
76
+ const form_data_1 = __importDefault(require("form-data"));
77
+ const lodash_1 = require("lodash");
78
+ const mime_types_1 = require("mime-types");
79
+ const oauth_1_0a_1 = __importDefault(require("oauth-1.0a"));
80
+ const qs_1 = require("qs");
81
+ const simple_oauth2_1 = require("simple-oauth2");
82
+ const __1 = require("..");
83
+ const BinaryDataManager_1 = require("./BinaryDataManager");
84
+ axios_1.default.defaults.timeout = 900000;
85
+ /**
86
+ * Creates an HTTPS agent for requests that need to skip standard SSL certificate
87
+ * validation. If the environment variable `NODE_CUSTOM_CA_CERT_PATH` is set, the
88
+ * agent will use that CA certificate instead of fully disabling validation — this
89
+ * is the recommended approach for self-signed or internal certificates.
90
+ *
91
+ * Falls back to `rejectUnauthorized: false` only when no custom CA is provided.
92
+ */
93
+ function createInsecureOrCustomCaAgent() {
94
+ const customCaPath = process.env["NODE_CUSTOM_CA_CERT_PATH"];
95
+ if (customCaPath) {
96
+ try {
97
+ const ca = (0, node_fs_1.readFileSync)(customCaPath);
98
+ return new node_https_1.Agent({ ca });
99
+ }
100
+ catch (error) {
101
+ workflow_1.LoggerProxy.warn(`Failed to read custom CA certificate from NODE_CUSTOM_CA_CERT_PATH ("${customCaPath}"). Falling back to disabling SSL certificate validation.`, { error });
102
+ }
103
+ }
104
+ workflow_1.LoggerProxy.warn("SSL certificate validation is disabled for this request. This is strongly discouraged in production. Set the NODE_CUSTOM_CA_CERT_PATH environment variable to a CA certificate file to avoid disabling validation entirely.");
105
+ // CodeQL: This is intentional — the caller explicitly opted in to skipping
106
+ // certificate validation (e.g. for self-signed certs in internal environments).
107
+ // The preferred alternative (custom CA via NODE_CUSTOM_CA_CERT_PATH) is checked above.
108
+ return new node_https_1.Agent({ rejectUnauthorized: false }); // lgtm[js/disabling-certificate-validation]
109
+ }
110
+ // Prevent axios from adding x-form-www-urlencoded headers by default
111
+ axios_1.default.defaults.headers.post = {};
112
+ axios_1.default.defaults.headers.put = {};
113
+ axios_1.default.defaults.headers.patch = {};
114
+ axios_1.default.defaults.paramsSerializer = (params) => {
115
+ if (params instanceof node_url_1.URLSearchParams) {
116
+ return params.toString();
117
+ }
118
+ return (0, qs_1.stringify)(params, { arrayFormat: "indices" });
119
+ };
120
+ const pushFormDataValue = (form, key, value) => {
121
+ if (value != null &&
122
+ Object.hasOwn(value, "value") &&
123
+ Object.hasOwn(value, "options")) {
124
+ // @ts-expect-error
125
+ form.append(key, value.value, value.options);
126
+ }
127
+ else {
128
+ form.append(key, value);
129
+ }
130
+ };
131
+ const createFormDataObject = (data) => {
132
+ const formData = new form_data_1.default();
133
+ const keys = Object.keys(data);
134
+ for (const key of keys) {
135
+ // @ts-expect-error
136
+ const formField = data[key];
137
+ if (Array.isArray(formField)) {
138
+ for (const item of formField) {
139
+ pushFormDataValue(formData, key, item);
140
+ }
141
+ }
142
+ else {
143
+ pushFormDataValue(formData, key, formField);
144
+ }
145
+ }
146
+ return formData;
147
+ };
148
+ function searchForHeader(headers, headerName) {
149
+ if (headers === undefined) {
150
+ return undefined;
151
+ }
152
+ const headerNames = Object.keys(headers);
153
+ const lowerHeaderName = headerName.toLowerCase();
154
+ return headerNames.find((thisHeader) => thisHeader.toLowerCase() === lowerHeaderName);
155
+ }
156
+ async function generateContentLengthHeader(formData, headers) {
157
+ if (!formData || !formData.getLength) {
158
+ return;
159
+ }
160
+ try {
161
+ const length = await new Promise((res, rej) => {
162
+ formData.getLength((error, length) => {
163
+ if (error) {
164
+ rej(error);
165
+ return;
166
+ }
167
+ res(length);
168
+ });
169
+ });
170
+ Object.assign(headers, {
171
+ "content-length": length,
172
+ });
173
+ }
174
+ catch (error) {
175
+ workflow_1.LoggerProxy.error("Unable to calculate form data length", { error });
176
+ }
177
+ }
178
+ async function parseRequestObject(requestObject) {
179
+ // This function is a temporary implementation
180
+ // That translates all http requests done via
181
+ // the request library to axios directly
182
+ // We are not using n8n's interface as it would
183
+ // an unnecessary step, considering the `request`
184
+ // helper can be deprecated and removed.
185
+ const axiosConfig = {};
186
+ if (typeof requestObject["headers"] === "object" &&
187
+ requestObject["headers"] !== null) {
188
+ axiosConfig.headers = requestObject["headers"];
189
+ }
190
+ // Let's start parsing the hardest part, which is the request body.
191
+ // The process here is as following?
192
+ // - Check if we have a `content-type` header. If this was set,
193
+ // we will follow
194
+ // - Check if the `form` property was set. If yes, then it's x-www-form-urlencoded
195
+ // - Check if the `formData` property exists. If yes, then it's multipart/form-data
196
+ // - Lastly, we should have a regular `body` that is probably a JSON.
197
+ const contentTypeHeaderKeyName = axiosConfig.headers &&
198
+ Object.keys(axiosConfig.headers).find((headerName) => headerName.toLowerCase() === "content-type");
199
+ const contentType = contentTypeHeaderKeyName &&
200
+ axiosConfig.headers &&
201
+ axiosConfig.headers[contentTypeHeaderKeyName];
202
+ if (contentType === "application/x-www-form-urlencoded" &&
203
+ requestObject["formData"] === undefined) {
204
+ // there are nodes incorrectly created, informing the content type header
205
+ // and also using formData. Request lib takes precedence for the formData.
206
+ // We will do the same.
207
+ // Merge body and form properties.
208
+ if (typeof requestObject["body"] === "string") {
209
+ axiosConfig.data = requestObject["body"];
210
+ }
211
+ else {
212
+ const allData = Object.assign(requestObject["body"] || {}, requestObject["form"] || {});
213
+ if (requestObject["useQuerystring"] === true) {
214
+ axiosConfig.data = (0, qs_1.stringify)(allData, { arrayFormat: "repeat" });
215
+ }
216
+ else {
217
+ axiosConfig.data = (0, qs_1.stringify)(allData);
218
+ }
219
+ }
220
+ }
221
+ else if (contentType &&
222
+ contentType.includes("multipart/form-data") !== false) {
223
+ if (requestObject["formData"] !== undefined &&
224
+ requestObject["formData"] instanceof form_data_1.default) {
225
+ axiosConfig.data = requestObject["formData"];
226
+ }
227
+ else {
228
+ const allData = {
229
+ ...requestObject["body"],
230
+ ...requestObject["formData"],
231
+ };
232
+ axiosConfig.data = createFormDataObject(allData);
233
+ }
234
+ // replace the existing header with a new one that
235
+ // contains the boundary property.
236
+ // @ts-expect-error
237
+ delete axiosConfig.headers[contentTypeHeaderKeyName];
238
+ const headers = axiosConfig.data.getHeaders();
239
+ axiosConfig.headers = Object.assign(axiosConfig.headers || {}, headers);
240
+ if (!axiosConfig.headers) {
241
+ axiosConfig.headers = {};
242
+ }
243
+ await generateContentLengthHeader(axiosConfig.data, axiosConfig.headers);
244
+ }
245
+ else {
246
+ // When using the `form` property it means the content should be x-www-form-urlencoded.
247
+ if (requestObject["form"] !== undefined &&
248
+ requestObject["body"] === undefined) {
249
+ // If we have only form
250
+ axiosConfig.data =
251
+ typeof requestObject["form"] === "string"
252
+ ? (0, qs_1.stringify)(requestObject["form"], { format: "RFC3986" })
253
+ : (0, qs_1.stringify)(requestObject["form"]).toString();
254
+ if (axiosConfig.headers !== undefined) {
255
+ const headerName = searchForHeader(axiosConfig.headers, "content-type");
256
+ if (headerName) {
257
+ delete axiosConfig.headers[headerName];
258
+ }
259
+ axiosConfig.headers["Content-Type"] =
260
+ "application/x-www-form-urlencoded";
261
+ }
262
+ else {
263
+ axiosConfig.headers = {
264
+ "Content-Type": "application/x-www-form-urlencoded",
265
+ };
266
+ }
267
+ }
268
+ else if (requestObject["formData"] !== undefined) {
269
+ // remove any "content-type" that might exist.
270
+ if (axiosConfig.headers !== undefined) {
271
+ const headers = Object.keys(axiosConfig.headers);
272
+ for (const header of headers) {
273
+ if (header.toLowerCase() === "content-type" && axiosConfig.headers) {
274
+ delete axiosConfig.headers[header];
275
+ }
276
+ }
277
+ }
278
+ if (requestObject["formData"] instanceof form_data_1.default) {
279
+ axiosConfig.data = requestObject["formData"];
280
+ }
281
+ else {
282
+ axiosConfig.data = createFormDataObject(requestObject["formData"]);
283
+ }
284
+ // Mix in headers as FormData creates the boundary.
285
+ const headers = axiosConfig.data.getHeaders();
286
+ axiosConfig.headers = Object.assign(axiosConfig.headers || {}, headers);
287
+ await generateContentLengthHeader(axiosConfig.data, axiosConfig.headers);
288
+ }
289
+ else if (requestObject["body"] !== undefined) {
290
+ // If we have body and possibly form
291
+ if (requestObject["form"] !== undefined) {
292
+ // merge both objects when exist.
293
+ if (requestObject["body"] && requestObject["form"]) {
294
+ requestObject["body"] = Object.assign(requestObject["body"], requestObject["form"]);
295
+ }
296
+ }
297
+ axiosConfig.data = requestObject["body"];
298
+ }
299
+ }
300
+ if (requestObject["uri"] !== undefined) {
301
+ axiosConfig.url = requestObject["uri"]?.toString();
302
+ }
303
+ if (requestObject["url"] !== undefined) {
304
+ axiosConfig.url = requestObject["url"]?.toString();
305
+ }
306
+ if (requestObject["baseURL"] !== undefined) {
307
+ axiosConfig.baseURL = requestObject["baseURL"]?.toString();
308
+ }
309
+ if (requestObject["method"] !== undefined) {
310
+ axiosConfig.method = requestObject["method"];
311
+ }
312
+ if (requestObject["qs"] !== undefined &&
313
+ Object.keys(requestObject["qs"]).length > 0) {
314
+ axiosConfig.params = requestObject["qs"];
315
+ }
316
+ if (requestObject["useQuerystring"] === true ||
317
+ // @ts-expect-error
318
+ requestObject.qsStringifyOptions?.arrayFormat === "repeat") {
319
+ axiosConfig.paramsSerializer = (params) => {
320
+ return (0, qs_1.stringify)(params, { arrayFormat: "repeat" });
321
+ };
322
+ }
323
+ else if (requestObject["useQuerystring"] === false) {
324
+ axiosConfig.paramsSerializer = (params) => {
325
+ return (0, qs_1.stringify)(params, { arrayFormat: "indices" });
326
+ };
327
+ }
328
+ // @ts-expect-error
329
+ if (requestObject.qsStringifyOptions?.arrayFormat === "brackets") {
330
+ axiosConfig.paramsSerializer = (params) => {
331
+ return (0, qs_1.stringify)(params, { arrayFormat: "brackets" });
332
+ };
333
+ }
334
+ if (requestObject["auth"] !== undefined) {
335
+ // Check support for sendImmediately
336
+ if (requestObject["auth"]["bearer"] !== undefined) {
337
+ axiosConfig.headers = Object.assign(axiosConfig.headers || {}, {
338
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
339
+ Authorization: `Bearer ${requestObject["auth"]["bearer"]}`,
340
+ });
341
+ }
342
+ else {
343
+ const authObj = requestObject["auth"];
344
+ // Request accepts both user/username and pass/password
345
+ axiosConfig.auth = {
346
+ username: (authObj["user"] || authObj["username"]),
347
+ password: (authObj["password"] || authObj["pass"]),
348
+ };
349
+ }
350
+ }
351
+ // Only set header if we have a body, otherwise it may fail
352
+ if (requestObject["json"] === true) {
353
+ // Add application/json headers - do not set charset as it breaks a lot of stuff
354
+ // only add if no other accept headers was sent.
355
+ const acceptHeaderExists = axiosConfig.headers === undefined
356
+ ? false
357
+ : Object.keys(axiosConfig.headers)
358
+ .map((headerKey) => headerKey.toLowerCase())
359
+ .includes("accept");
360
+ if (!acceptHeaderExists) {
361
+ axiosConfig.headers = Object.assign(axiosConfig.headers || {}, {
362
+ Accept: "application/json",
363
+ });
364
+ }
365
+ }
366
+ if (requestObject["json"] === false || requestObject["json"] === undefined) {
367
+ // Prevent json parsing
368
+ axiosConfig.transformResponse = (res) => res;
369
+ }
370
+ // Axios will follow redirects by default, so we simply tell it otherwise if needed.
371
+ if (requestObject["followRedirect"] === false &&
372
+ (requestObject["method"] || "get").toLowerCase() ===
373
+ "get") {
374
+ axiosConfig.maxRedirects = 0;
375
+ }
376
+ if (requestObject["followAllRedirects"] === false &&
377
+ (requestObject["method"] || "get").toLowerCase() !==
378
+ "get") {
379
+ axiosConfig.maxRedirects = 0;
380
+ }
381
+ if (requestObject["rejectUnauthorized"] === false) {
382
+ axiosConfig.httpsAgent = createInsecureOrCustomCaAgent();
383
+ }
384
+ if (requestObject["timeout"] !== undefined) {
385
+ axiosConfig.timeout = requestObject["timeout"];
386
+ }
387
+ if (requestObject["proxy"] !== undefined) {
388
+ // try our best to parse the url provided.
389
+ if (typeof requestObject["proxy"] === "string") {
390
+ try {
391
+ const url = new node_url_1.URL(requestObject["proxy"]);
392
+ axiosConfig.proxy = {
393
+ host: url.hostname,
394
+ port: Number.parseInt(url.port, 10),
395
+ protocol: url.protocol,
396
+ };
397
+ if (!url.port) {
398
+ // Sets port to a default if not informed
399
+ if (url.protocol === "http") {
400
+ axiosConfig.proxy.port = 80;
401
+ }
402
+ else if (url.protocol === "https") {
403
+ axiosConfig.proxy.port = 443;
404
+ }
405
+ }
406
+ if (url.username || url.password) {
407
+ axiosConfig.proxy.auth = {
408
+ username: url.username,
409
+ password: url.password,
410
+ };
411
+ }
412
+ }
413
+ catch (_error) {
414
+ // Not a valid URL. We will try to simply parse stuff
415
+ // such as user:pass@host:port without protocol (we'll assume http)
416
+ if (requestObject["proxy"].includes("@")) {
417
+ const [userpass, hostport] = requestObject["proxy"].split("@");
418
+ const [username, password] = userpass.split(":");
419
+ const [hostname, port] = hostport.split(":");
420
+ axiosConfig.proxy = {
421
+ host: hostname,
422
+ port: Number.parseInt(port, 10),
423
+ protocol: "http",
424
+ auth: {
425
+ username,
426
+ password,
427
+ },
428
+ };
429
+ }
430
+ else if (requestObject["proxy"].includes(":")) {
431
+ const [hostname, port] = requestObject["proxy"].split(":");
432
+ axiosConfig.proxy = {
433
+ host: hostname,
434
+ port: Number.parseInt(port, 10),
435
+ protocol: "http",
436
+ };
437
+ }
438
+ else {
439
+ axiosConfig.proxy = {
440
+ host: requestObject["proxy"],
441
+ port: 80,
442
+ protocol: "http",
443
+ };
444
+ }
445
+ }
446
+ }
447
+ else {
448
+ axiosConfig.proxy = requestObject["proxy"];
449
+ }
450
+ }
451
+ if (requestObject["encoding"] === null) {
452
+ // When downloading files, return an arrayBuffer.
453
+ axiosConfig.responseType = "arraybuffer";
454
+ }
455
+ // If we don't set an accept header
456
+ // Axios forces "application/json, text/plan, */*"
457
+ // Which causes some nodes like NextCloud to break
458
+ // as the service returns XML unless requested otherwise.
459
+ const allHeaders = axiosConfig.headers
460
+ ? Object.keys(axiosConfig.headers)
461
+ : [];
462
+ if (!allHeaders.some((headerKey) => headerKey.toLowerCase() === "accept")) {
463
+ axiosConfig.headers = Object.assign(axiosConfig.headers || {}, {
464
+ accept: "*/*",
465
+ });
466
+ }
467
+ if (requestObject["json"] !== false &&
468
+ axiosConfig.data !== undefined &&
469
+ axiosConfig.data !== "" &&
470
+ !(axiosConfig.data instanceof Buffer) &&
471
+ !allHeaders.some((headerKey) => headerKey.toLowerCase() === "content-type")) {
472
+ // Use default header for application/json
473
+ // If we don't specify this here, axios will add
474
+ // application/json; charset=utf-8
475
+ // and this breaks a lot of stuff
476
+ axiosConfig.headers = Object.assign(axiosConfig.headers || {}, {
477
+ "content-type": "application/json",
478
+ });
479
+ }
480
+ /**
481
+ * Missing properties:
482
+ * encoding (need testing)
483
+ * gzip (ignored - default already works)
484
+ * resolveWithFullResponse (implemented elsewhere)
485
+ * simple (???)
486
+ */
487
+ return axiosConfig;
488
+ }
489
+ function digestAuthAxiosConfig(axiosConfig, response, auth) {
490
+ const authDetails = response.headers["www-authenticate"]
491
+ .split(",")
492
+ .map((v) => v.split("="));
493
+ if (authDetails) {
494
+ const nonceCount = "000000001";
495
+ const cnonce = crypto.randomBytes(24).toString("hex");
496
+ const findDetail = (key) => {
497
+ const entry = authDetails.find((el) => el[0].toLowerCase().indexOf(key) > -1);
498
+ return entry?.[1]?.replace(/"/g, "");
499
+ };
500
+ const realm = findDetail("realm") ?? "";
501
+ const opaque = findDetail("opaque") ?? "";
502
+ const nonce = findDetail("nonce") ?? "";
503
+ // RFC 7616: Support SHA-256 when the server advertises it, falling back
504
+ // to MD5 only when required by the server (RFC 2617 legacy). MD5 is
505
+ // flagged by security scanners as insufficient computational effort for
506
+ // password hashing, so we prefer SHA-256 whenever available.
507
+ const serverAlgorithm = (findDetail("algorithm") ?? "MD5").toUpperCase();
508
+ const hashAlgorithm = serverAlgorithm === "SHA-256" ? "sha256" : "md5";
509
+ const algorithmLabel = serverAlgorithm === "SHA-256" ? "SHA-256" : "MD5";
510
+ const ha1 = crypto
511
+ .createHash(hashAlgorithm)
512
+ .update(`${auth?.username}:${realm}:${auth?.password}`)
513
+ .digest("hex");
514
+ const path = new url.URL(axiosConfig.url).pathname;
515
+ const ha2 = crypto
516
+ .createHash(hashAlgorithm)
517
+ .update(`${axiosConfig.method ?? "GET"}:${path}`)
518
+ .digest("hex");
519
+ const response = crypto
520
+ .createHash(hashAlgorithm)
521
+ .update(`${ha1}:${nonce}:${nonceCount}:${cnonce}:auth:${ha2}`)
522
+ .digest("hex");
523
+ const authorization = `Digest username="${auth?.username}",realm="${realm}",` +
524
+ `nonce="${nonce}",uri="${path}",qop="auth",algorithm="${algorithmLabel}",` +
525
+ `response="${response}",nc="${nonceCount}",cnonce="${cnonce}",opaque="${opaque}"`;
526
+ if (axiosConfig.headers) {
527
+ axiosConfig.headers["authorization"] = authorization;
528
+ }
529
+ else {
530
+ axiosConfig.headers = { authorization };
531
+ }
532
+ }
533
+ return axiosConfig;
534
+ }
535
+ async function proxyRequestToAxios(uriOrObject, options) {
536
+ let axiosConfig = {};
537
+ let axiosPromise;
538
+ let configObject;
539
+ if (uriOrObject !== undefined && typeof uriOrObject === "string") {
540
+ axiosConfig.url = uriOrObject;
541
+ }
542
+ if (uriOrObject !== undefined &&
543
+ typeof uriOrObject === "object" &&
544
+ uriOrObject !== null) {
545
+ configObject = uriOrObject;
546
+ }
547
+ else {
548
+ configObject = options || {};
549
+ }
550
+ axiosConfig = Object.assign(axiosConfig, await parseRequestObject(configObject));
551
+ workflow_1.LoggerProxy.debug("Proxying request to axios");
552
+ if (configObject.auth?.sendImmediately === false) {
553
+ // for digest-auth
554
+ const { auth } = axiosConfig;
555
+ axiosConfig.auth = undefined;
556
+ axiosPromise = (async () => {
557
+ try {
558
+ return await (0, axios_1.default)(axiosConfig);
559
+ }
560
+ catch (resp) {
561
+ const axiosError = resp;
562
+ if (axiosError.response === undefined ||
563
+ axiosError.response.status !== 401 ||
564
+ !axiosError.response.headers["www-authenticate"]?.includes("nonce")) {
565
+ throw resp;
566
+ }
567
+ axiosConfig = digestAuthAxiosConfig(axiosConfig, axiosError.response, auth);
568
+ return (0, axios_1.default)(axiosConfig);
569
+ }
570
+ })();
571
+ }
572
+ else {
573
+ axiosPromise = (0, axios_1.default)(axiosConfig);
574
+ }
575
+ return new Promise((resolve, reject) => {
576
+ axiosPromise
577
+ .then((response) => {
578
+ if (configObject.resolveWithFullResponse === true) {
579
+ let body = response.data;
580
+ if (response.data === "") {
581
+ if (axiosConfig.responseType === "arraybuffer") {
582
+ body = Buffer.alloc(0);
583
+ }
584
+ else {
585
+ body = undefined;
586
+ }
587
+ }
588
+ resolve({
589
+ body,
590
+ headers: response.headers,
591
+ statusCode: response.status,
592
+ statusMessage: response.statusText,
593
+ request: response.request,
594
+ });
595
+ }
596
+ else {
597
+ let body = response.data;
598
+ if (response.data === "") {
599
+ if (axiosConfig.responseType === "arraybuffer") {
600
+ body = Buffer.alloc(0);
601
+ }
602
+ else {
603
+ body = undefined;
604
+ }
605
+ }
606
+ resolve(body);
607
+ }
608
+ })
609
+ .catch((error) => {
610
+ if (configObject.simple === false && error.response) {
611
+ if (configObject.resolveWithFullResponse) {
612
+ resolve({
613
+ body: error.response.data,
614
+ headers: error.response.headers,
615
+ statusCode: error.response.status,
616
+ statusMessage: error.response.statusText,
617
+ });
618
+ }
619
+ else {
620
+ resolve(error.response.data);
621
+ }
622
+ return;
623
+ }
624
+ workflow_1.LoggerProxy.debug("Request proxied to Axios failed", { error });
625
+ // Axios hydrates the original error with more data. We extract them.
626
+ // https://github.com/axios/axios/blob/master/lib/core/enhanceError.js
627
+ // Note: `code` is ignored as it's an expected part of the errorData.
628
+ const { request, response, isAxiosError, toJSON, config, ...errorData } = error;
629
+ if (response) {
630
+ error.message = `${response.status} - ${JSON.stringify(response.data)}`;
631
+ }
632
+ error.cause = errorData;
633
+ error.error = error.response?.data || errorData;
634
+ error.statusCode = error.response?.status;
635
+ error.options = config || {};
636
+ // Remove not needed data and so also remove circular references
637
+ error.request = undefined;
638
+ error.config = undefined;
639
+ error.options.adapter = undefined;
640
+ error.options.httpsAgent = undefined;
641
+ error.options.paramsSerializer = undefined;
642
+ error.options.transformRequest = undefined;
643
+ error.options.transformResponse = undefined;
644
+ error.options.validateStatus = undefined;
645
+ reject(error);
646
+ });
647
+ });
648
+ }
649
+ function convertN8nRequestToAxios(n8nRequest) {
650
+ // Destructure properties with the same name first.
651
+ const { headers, method, timeout, auth, proxy, url } = n8nRequest;
652
+ const axiosRequest = {
653
+ headers: headers ?? {},
654
+ method,
655
+ timeout,
656
+ auth,
657
+ proxy,
658
+ url,
659
+ };
660
+ axiosRequest.params = n8nRequest.qs;
661
+ if (n8nRequest.baseURL !== undefined) {
662
+ axiosRequest.baseURL = n8nRequest.baseURL;
663
+ }
664
+ if (n8nRequest.disableFollowRedirect === true) {
665
+ axiosRequest.maxRedirects = 0;
666
+ }
667
+ if (n8nRequest.encoding !== undefined) {
668
+ axiosRequest.responseType = n8nRequest.encoding;
669
+ }
670
+ if (n8nRequest.skipSslCertificateValidation === true) {
671
+ axiosRequest.httpsAgent = createInsecureOrCustomCaAgent();
672
+ }
673
+ if (n8nRequest.arrayFormat !== undefined) {
674
+ axiosRequest.paramsSerializer = (params) => {
675
+ return (0, qs_1.stringify)(params, { arrayFormat: n8nRequest.arrayFormat });
676
+ };
677
+ }
678
+ if (n8nRequest.body) {
679
+ axiosRequest.data = n8nRequest.body;
680
+ // Let's add some useful header standards here.
681
+ const existingContentTypeHeaderKey = axiosRequest.headers
682
+ ? searchForHeader(axiosRequest.headers, "content-type")
683
+ : undefined;
684
+ if (existingContentTypeHeaderKey === undefined) {
685
+ // We are only setting content type headers if the user did
686
+ // not set it already manually. We're not overriding, even if it's wrong.
687
+ if (axiosRequest.data instanceof form_data_1.default) {
688
+ axiosRequest.headers = axiosRequest.headers || {};
689
+ axiosRequest.headers["Content-Type"] = "multipart/form-data";
690
+ }
691
+ else if (axiosRequest.data instanceof node_url_1.URLSearchParams) {
692
+ axiosRequest.headers = axiosRequest.headers || {};
693
+ axiosRequest.headers["Content-Type"] =
694
+ "application/x-www-form-urlencoded";
695
+ }
696
+ }
697
+ }
698
+ if (n8nRequest.json) {
699
+ const key = axiosRequest.headers
700
+ ? searchForHeader(axiosRequest.headers, "accept")
701
+ : undefined;
702
+ // If key exists, then the user has set both accept
703
+ // header and the json flag. Header should take precedence.
704
+ if (!key) {
705
+ axiosRequest.headers = axiosRequest.headers || {};
706
+ axiosRequest.headers.Accept = "application/json";
707
+ }
708
+ }
709
+ const userAgentHeader = axiosRequest.headers
710
+ ? searchForHeader(axiosRequest.headers, "user-agent")
711
+ : undefined;
712
+ // If key exists, then the user has set both accept
713
+ // header and the json flag. Header should take precedence.
714
+ if (!userAgentHeader) {
715
+ axiosRequest.headers = axiosRequest.headers || {};
716
+ axiosRequest.headers["User-Agent"] = "n8n";
717
+ }
718
+ if (n8nRequest.ignoreHttpStatusErrors) {
719
+ axiosRequest.validateStatus = () => true;
720
+ }
721
+ return axiosRequest;
722
+ }
723
+ async function httpRequest(requestOptions) {
724
+ const axiosRequest = convertN8nRequestToAxios(requestOptions);
725
+ const result = await (0, axios_1.default)(axiosRequest);
726
+ if (requestOptions.returnFullResponse) {
727
+ return {
728
+ body: result.data,
729
+ headers: result.headers,
730
+ statusCode: result.status,
731
+ statusMessage: result.statusText,
732
+ };
733
+ }
734
+ return result.data;
735
+ }
736
+ /**
737
+ * Returns binary data buffer for given item index and property name.
738
+ *
739
+ * @export
740
+ * @param {ITaskDataConnections} inputData
741
+ * @param {number} itemIndex
742
+ * @param {string} propertyName
743
+ * @param {number} inputIndex
744
+ * @returns {Promise<Buffer>}
745
+ */
746
+ async function getBinaryDataBuffer(inputData, itemIndex, propertyName, inputIndex) {
747
+ const binaryData = inputData["main"]?.[inputIndex]?.[itemIndex]?.binary?.[propertyName];
748
+ if (!binaryData) {
749
+ throw new Error(`No binary data property "${propertyName}" exists on item at index ${itemIndex}`);
750
+ }
751
+ return BinaryDataManager_1.BinaryDataManager.getInstance().retrieveBinaryData(binaryData);
752
+ }
753
+ /**
754
+ * Takes a buffer and converts it into the format n8n uses. It encodes the binary data as
755
+ * base64 and adds metadata.
756
+ *
757
+ * @export
758
+ * @param {Buffer} binaryData
759
+ * @param {string} [filePath]
760
+ * @param {string} [mimeType]
761
+ * @returns {Promise<IBinaryData>}
762
+ */
763
+ async function prepareBinaryData(binaryData, executionId, filePath, mimeType) {
764
+ let resolvedMimeType = mimeType;
765
+ if (!resolvedMimeType) {
766
+ // If no mime type is given figure it out
767
+ if (filePath) {
768
+ // Use file path to guess mime type
769
+ const mimeTypeLookup = (0, mime_types_1.lookup)(filePath);
770
+ if (mimeTypeLookup) {
771
+ resolvedMimeType = mimeTypeLookup;
772
+ }
773
+ }
774
+ if (!resolvedMimeType) {
775
+ // Use buffer to guess mime type
776
+ const { fileTypeFromBuffer } = await Promise.resolve().then(() => __importStar(require("file-type")));
777
+ const fileTypeData = await fileTypeFromBuffer(binaryData);
778
+ if (fileTypeData) {
779
+ resolvedMimeType = fileTypeData.mime;
780
+ }
781
+ }
782
+ if (!resolvedMimeType) {
783
+ // Fall back to text
784
+ resolvedMimeType = "text/plain";
785
+ }
786
+ }
787
+ const returnData = {
788
+ mimeType: resolvedMimeType,
789
+ data: "",
790
+ };
791
+ let resolvedFilePath = filePath;
792
+ if (resolvedFilePath) {
793
+ if (resolvedFilePath.includes("?")) {
794
+ // Remove maybe present query parameters
795
+ resolvedFilePath = resolvedFilePath.split("?").shift();
796
+ }
797
+ const filePathParts = path.parse(resolvedFilePath);
798
+ if (filePathParts.dir !== "") {
799
+ returnData.directory = filePathParts.dir;
800
+ }
801
+ returnData.fileName = filePathParts.base;
802
+ // Remove the dot
803
+ const fileExtension = filePathParts.ext.slice(1);
804
+ if (fileExtension) {
805
+ returnData.fileExtension = fileExtension;
806
+ }
807
+ }
808
+ return BinaryDataManager_1.BinaryDataManager.getInstance().storeBinaryData(returnData, binaryData, executionId);
809
+ }
810
+ async function getCurrentOAuth2AccessToken(credentialsType, node, additionalData, oAuth2Options) {
811
+ const credentials = (await this.getCredentials(credentialsType));
812
+ if (!credentials) {
813
+ throw new workflow_1.NodeOperationError(node, `No credentials of type "${credentialsType}" were returned!`, { description: "Credentials not found" });
814
+ }
815
+ const clientId = credentials["clientId"];
816
+ const clientSecret = credentials["clientSecret"];
817
+ const scope = credentials["scope"];
818
+ const grantType = credentials["grantType"];
819
+ const authUrl = credentials["authUrl"];
820
+ const accessTokenUrl = credentials["accessTokenUrl"];
821
+ const httpAgent = credentials["httpAgent"];
822
+ const oauthTokenData = (credentials["oauthTokenData"] ?? {});
823
+ let accessToken = (oauthTokenData?.["accessToken"] ?? "");
824
+ let refreshToken = (oauthTokenData?.["refreshToken"] ?? "");
825
+ let expiresAt = (oauthTokenData?.["expiresAt"] ?? "");
826
+ let expiresIn = (oauthTokenData?.["expiresIn"] ?? 0);
827
+ let tokenType = (oauthTokenData?.["tokenType"] ?? "");
828
+ const isExpired = !accessToken || !expiresAt || new Date(expiresAt).getTime() <= Date.now();
829
+ if (isExpired) {
830
+ workflow_1.LoggerProxy.debug(`Node "${node.name}" is using credential "${credentialsType}", however the access token has expired. This will now be refreshed using the "${grantType}" flow.`);
831
+ if (!authUrl) {
832
+ throw new workflow_1.NodeOperationError(node, `The "authUrl" property is required for the "${grantType}" flow. Please check your credentials configuration.`, { description: "Missing authUrl" });
833
+ }
834
+ const config = {
835
+ client: { id: clientId, secret: clientSecret },
836
+ auth: { tokenHost: authUrl, tokenPath: accessTokenUrl },
837
+ http: { agent: httpAgent },
838
+ };
839
+ if (grantType === "authorizationCode") {
840
+ const client = new simple_oauth2_1.AuthorizationCode(config);
841
+ let token = client.createToken({
842
+ access_token: (0, lodash_1.get)(oauthTokenData, oAuth2Options?.property) || accessToken,
843
+ refresh_token: refreshToken,
844
+ token_type: oAuth2Options?.tokenType || tokenType || "Bearer",
845
+ expires_at: expiresAt,
846
+ expires_in: expiresIn,
847
+ });
848
+ const refreshOptions = oAuth2Options?.includeCredentialsOnRefreshOnBody
849
+ ? {
850
+ body: {
851
+ client_id: clientId,
852
+ client_secret: clientSecret,
853
+ },
854
+ headers: {
855
+ Authorization: "", // override default basic auth
856
+ },
857
+ }
858
+ : {};
859
+ token = await token.refresh(refreshOptions);
860
+ accessToken = token.token["access_token"];
861
+ refreshToken = token.token["refresh_token"] || refreshToken;
862
+ expiresAt = token.token["expires_at"];
863
+ expiresIn = token.token["expires_in"];
864
+ tokenType = token.token["token_type"] || "Bearer";
865
+ }
866
+ else if (grantType === "clientCredentials") {
867
+ const client = new simple_oauth2_1.ClientCredentials(config);
868
+ const token = await client.getToken({ scope });
869
+ accessToken = token.token["access_token"];
870
+ refreshToken = token.token["refresh_token"] || refreshToken;
871
+ expiresAt = token.token["expires_at"];
872
+ expiresIn = token.token["expires_in"];
873
+ tokenType = token.token["token_type"] || "Bearer";
874
+ }
875
+ else {
876
+ throw new Error(`Unsupported grant type "${grantType}" for Microsoft Graph API`);
877
+ }
878
+ oauthTokenData["accessToken"] = accessToken;
879
+ oauthTokenData["refreshToken"] = refreshToken;
880
+ oauthTokenData["expiresAt"] = expiresAt;
881
+ oauthTokenData["expiresIn"] = expiresIn;
882
+ oauthTokenData["tokenType"] = tokenType;
883
+ if (!node.credentials || !node.credentials[credentialsType]) {
884
+ throw new Error(`Node "${node.name}" has no credentials of type "${credentialsType}"`);
885
+ }
886
+ await additionalData.credentialsHelper.updateCredentials(node.credentials[credentialsType], credentialsType, credentials);
887
+ workflow_1.LoggerProxy.debug(`Access token for credential "${credentialsType}" has been refreshed.`);
888
+ }
889
+ return accessToken;
890
+ }
891
+ async function requestOAuth2(credentialsType, requestOptions, node, additionalData, oAuth2Options, isN8nRequest = false) {
892
+ const credentials = (await this.getCredentials(credentialsType));
893
+ if (!credentials) {
894
+ throw new Error("No credentials were returned!");
895
+ }
896
+ const oauthTokenData = credentials["oauthTokenData"];
897
+ if (!oauthTokenData) {
898
+ throw new Error("OAuth credentials not connected!");
899
+ }
900
+ const config = {
901
+ client: {
902
+ id: credentials["clientId"],
903
+ secret: credentials["clientSecret"],
904
+ },
905
+ auth: {
906
+ tokenHost: credentials["authUrl"],
907
+ tokenPath: credentials["accessTokenUrl"],
908
+ },
909
+ http: {
910
+ agent: credentials["httpAgent"],
911
+ },
912
+ };
913
+ const client = new simple_oauth2_1.AuthorizationCode(config);
914
+ // Construct token from saved data
915
+ const tokenObject = client.createToken({
916
+ access_token: (0, lodash_1.get)(oauthTokenData, oAuth2Options?.property) ||
917
+ oauthTokenData["accessToken"],
918
+ refresh_token: oauthTokenData["refreshToken"],
919
+ token_type: oAuth2Options?.tokenType || oauthTokenData["tokenType"] || "Bearer",
920
+ expires_at: oauthTokenData["expiresAt"],
921
+ expires_in: oauthTokenData["expiresIn"],
922
+ });
923
+ const currentToken = tokenObject;
924
+ const signedOptions = {
925
+ ...requestOptions,
926
+ headers: {
927
+ ...(requestOptions.headers || {}),
928
+ Authorization: `${currentToken.token["token_type"]} ${currentToken.token["access_token"]}`,
929
+ },
930
+ };
931
+ // Remove 'Bearer' if `keepBearer` is false
932
+ if (oAuth2Options?.keepBearer === false &&
933
+ signedOptions.headers.Authorization) {
934
+ signedOptions.headers.Authorization =
935
+ signedOptions.headers.Authorization.split(" ")[1];
936
+ }
937
+ try {
938
+ const requestFn = this.helpers["request"];
939
+ return await requestFn?.(signedOptions);
940
+ }
941
+ catch (error) {
942
+ const statusCodeReturned = oAuth2Options?.tokenExpiredStatusCode ?? 401;
943
+ const err = error;
944
+ if (err.statusCode === statusCodeReturned && currentToken.expired()) {
945
+ // Refresh token
946
+ workflow_1.LoggerProxy.debug(`OAuth2 token for "${credentialsType}" used by node "${node.name}" expired. Refreshing...`);
947
+ workflow_1.LoggerProxy.info(`OAuth2 token for "${credentialsType}" used by node "${node.name}" expired. Refreshing...`);
948
+ const refreshOptions = {};
949
+ if (oAuth2Options?.includeCredentialsOnRefreshOnBody) {
950
+ refreshOptions["body"] = {
951
+ client_id: credentials["clientId"],
952
+ client_secret: credentials["clientSecret"],
953
+ };
954
+ refreshOptions["headers"] = {
955
+ Authorization: "", // override default basic auth
956
+ };
957
+ }
958
+ let refreshedToken;
959
+ try {
960
+ refreshedToken = await currentToken.refresh(refreshOptions);
961
+ }
962
+ catch (error) {
963
+ throw new Error(`Failed to refresh token: ${error instanceof Error ? error.message : String(error)}`);
964
+ }
965
+ credentials["oauthTokenData"] = {
966
+ accessToken: refreshedToken.token["access_token"],
967
+ refreshToken: refreshedToken.token["refresh_token"] ||
968
+ currentToken.token["refresh_token"],
969
+ expiresAt: refreshedToken.token["expires_at"],
970
+ expiresIn: refreshedToken.token["expires_in"],
971
+ tokenType: refreshedToken.token["token_type"],
972
+ };
973
+ // Persist new token
974
+ if (!node.credentials || !node.credentials[credentialsType]) {
975
+ throw new Error(`Node "${node.name}" has no credentials of type "${credentialsType}"`);
976
+ }
977
+ await additionalData.credentialsHelper.updateCredentials(node.credentials[credentialsType], credentialsType, credentials);
978
+ workflow_1.LoggerProxy.debug(`OAuth2 token for "${credentialsType}" used by node "${node.name}" has been renewed.`);
979
+ workflow_1.LoggerProxy.info(`OAuth2 token for "${credentialsType}" used by node "${node.name}" has been renewed.`);
980
+ // Retry the original request with the new token
981
+ const retryOptions = {
982
+ ...requestOptions,
983
+ headers: {
984
+ ...(requestOptions.headers || {}),
985
+ Authorization: `${refreshedToken.token["token_type"]} ${refreshedToken.token["access_token"]}`,
986
+ },
987
+ };
988
+ if (oAuth2Options?.keepBearer === false &&
989
+ retryOptions.headers.Authorization) {
990
+ retryOptions.headers.Authorization =
991
+ retryOptions.headers.Authorization.split(" ")[1];
992
+ }
993
+ const retryRequestFn = this.helpers["request"];
994
+ return isN8nRequest
995
+ ? this.helpers.httpRequest(retryOptions)
996
+ : retryRequestFn?.(retryOptions);
997
+ }
998
+ throw error;
999
+ }
1000
+ }
1001
+ /* Makes a request using OAuth1 data for authentication
1002
+ *
1003
+ * @export
1004
+ * @param {IAllExecuteFunctions} this
1005
+ * @param {string} credentialsType
1006
+ * @param {(OptionsWithUrl | RequestPromiseOptions)} requestOptionså
1007
+ * @returns
1008
+ */
1009
+ async function requestOAuth1(credentialsType, requestOptions, isN8nRequest = false) {
1010
+ const credentials = (await this.getCredentials(credentialsType));
1011
+ if (credentials === undefined) {
1012
+ throw new Error("No credentials were returned!");
1013
+ }
1014
+ if (credentials["oauthTokenData"] === undefined) {
1015
+ throw new Error("OAuth credentials not connected!");
1016
+ }
1017
+ const oauth = new oauth_1_0a_1.default({
1018
+ consumer: {
1019
+ key: credentials["consumerKey"],
1020
+ secret: credentials["consumerSecret"],
1021
+ },
1022
+ signature_method: credentials["signatureMethod"],
1023
+ hash_function(base, key) {
1024
+ const algorithm = credentials["signatureMethod"] === "HMAC-SHA1" ? "sha1" : "sha256";
1025
+ return (0, node_crypto_1.createHmac)(algorithm, key).update(base).digest("base64");
1026
+ },
1027
+ });
1028
+ const oauthTokenData = credentials["oauthTokenData"];
1029
+ const token = {
1030
+ key: oauthTokenData["oauth_token"],
1031
+ secret: oauthTokenData["oauth_token_secret"],
1032
+ };
1033
+ // @ts-expect-error
1034
+ requestOptions.data = { ...requestOptions.qs, ...requestOptions.form };
1035
+ // Fixes issue that OAuth1 library only works with "url" property and not with "uri"
1036
+ // @ts-expect-error
1037
+ if (requestOptions.uri && !requestOptions.url) {
1038
+ // @ts-expect-error
1039
+ requestOptions.url = requestOptions.uri;
1040
+ // @ts-expect-error
1041
+ requestOptions.uri = undefined;
1042
+ }
1043
+ // @ts-expect-error
1044
+ requestOptions.headers = oauth.toHeader(oauth.authorize(requestOptions, token));
1045
+ if (isN8nRequest) {
1046
+ return this.helpers.httpRequest(requestOptions);
1047
+ }
1048
+ const oauth1RequestFn = this.helpers["request"];
1049
+ return oauth1RequestFn?.(requestOptions)?.catch(async (error) => {
1050
+ // Unknown error so simply throw it
1051
+ throw error;
1052
+ });
1053
+ }
1054
+ async function httpRequestWithAuthentication(credentialsType, requestOptions, workflow, node, additionalData, additionalCredentialOptions) {
1055
+ try {
1056
+ const parentTypes = additionalData.credentialsHelper.getParentTypes(credentialsType);
1057
+ if (parentTypes.includes("oAuth1Api")) {
1058
+ return await requestOAuth1.call(this, credentialsType, requestOptions, true);
1059
+ }
1060
+ if (parentTypes.includes("oAuth2Api")) {
1061
+ return await requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, additionalCredentialOptions?.oauth2, true);
1062
+ }
1063
+ let credentialsDecrypted;
1064
+ if (additionalCredentialOptions?.credentialsDecrypted) {
1065
+ credentialsDecrypted =
1066
+ additionalCredentialOptions.credentialsDecrypted.data;
1067
+ }
1068
+ else {
1069
+ credentialsDecrypted = await this.getCredentials(credentialsType);
1070
+ }
1071
+ if (credentialsDecrypted === undefined) {
1072
+ throw new workflow_1.NodeOperationError(node, `Node "${node.name}" does not have any credentials of type "${credentialsType}" set!`);
1073
+ }
1074
+ const authenticatedOptions = await additionalData.credentialsHelper.authenticate(credentialsDecrypted, credentialsType, requestOptions, workflow, node);
1075
+ return await httpRequest(authenticatedOptions);
1076
+ }
1077
+ catch (error) {
1078
+ throw new workflow_1.NodeApiError(this.getNode(), error);
1079
+ }
1080
+ }
1081
+ /**
1082
+ * Takes generic input data and brings it into the json format n8n uses.
1083
+ *
1084
+ * @export
1085
+ * @param {(IDataObject | IDataObject[])} jsonData
1086
+ * @returns {INodeExecutionData[]}
1087
+ */
1088
+ function returnJsonArray(jsonData) {
1089
+ const returnData = [];
1090
+ const dataArray = Array.isArray(jsonData) ? jsonData : [jsonData];
1091
+ for (const data of dataArray) {
1092
+ returnData.push({ json: data });
1093
+ }
1094
+ return returnData;
1095
+ }
1096
+ /**
1097
+ * Automatically put the objects under a 'json' key and don't error,
1098
+ * if some objects contain json/binary keys and others don't, throws error 'Inconsistent item format'
1099
+ *
1100
+ * @export
1101
+ * @param {INodeExecutionData | INodeExecutionData[]} executionData
1102
+ * @returns {INodeExecutionData[]}
1103
+ */
1104
+ function normalizeItems(executionData) {
1105
+ const dataArray = typeof executionData === "object" && !Array.isArray(executionData)
1106
+ ? [{ json: executionData }]
1107
+ : executionData;
1108
+ if (dataArray.every((item) => typeof item === "object" && "json" in item))
1109
+ return dataArray;
1110
+ if (dataArray.some((item) => typeof item === "object" && "json" in item)) {
1111
+ throw new Error("Inconsistent item format");
1112
+ }
1113
+ if (dataArray.every((item) => typeof item === "object" && "binary" in item)) {
1114
+ const normalizedItems = [];
1115
+ for (const item of dataArray) {
1116
+ const json = {};
1117
+ for (const key of Object.keys(item)) {
1118
+ if (key !== "binary") {
1119
+ json[key] = item[key];
1120
+ }
1121
+ }
1122
+ normalizedItems.push({
1123
+ json,
1124
+ binary: item.binary,
1125
+ });
1126
+ }
1127
+ return normalizedItems;
1128
+ }
1129
+ if (dataArray.some((item) => typeof item === "object" && "binary" in item)) {
1130
+ throw new Error("Inconsistent item format");
1131
+ }
1132
+ return dataArray.map((item) => {
1133
+ return { json: item };
1134
+ });
1135
+ }
1136
+ // TODO: Move up later
1137
+ async function requestWithAuthentication(credentialsType, requestOptions, workflow, node, additionalData, additionalCredentialOptions) {
1138
+ try {
1139
+ const parentTypes = additionalData.credentialsHelper.getParentTypes(credentialsType);
1140
+ if (parentTypes.includes("oAuth1Api")) {
1141
+ return await requestOAuth1.call(this, credentialsType, requestOptions, false);
1142
+ }
1143
+ if (parentTypes.includes("oAuth2Api")) {
1144
+ return await requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, additionalCredentialOptions?.oauth2, false);
1145
+ }
1146
+ let credentialsDecrypted;
1147
+ if (additionalCredentialOptions?.credentialsDecrypted) {
1148
+ credentialsDecrypted =
1149
+ additionalCredentialOptions.credentialsDecrypted.data;
1150
+ }
1151
+ else {
1152
+ credentialsDecrypted = await this.getCredentials(credentialsType);
1153
+ }
1154
+ if (credentialsDecrypted === undefined) {
1155
+ throw new workflow_1.NodeOperationError(node, `Node "${node.name}" does not have any credentials of type "${credentialsType}" set!`);
1156
+ }
1157
+ const authenticatedOptions = await additionalData.credentialsHelper.authenticate(credentialsDecrypted, credentialsType, requestOptions, workflow, node);
1158
+ return await proxyRequestToAxios(authenticatedOptions);
1159
+ }
1160
+ catch (error) {
1161
+ throw new workflow_1.NodeApiError(this.getNode(), error);
1162
+ }
1163
+ }
1164
+ /**
1165
+ * Returns the additional keys for Expressions and Function-Nodes
1166
+ *
1167
+ * @export
1168
+ * @param {IWorkflowExecuteAdditionalData} additionalData
1169
+ * @returns {(IWorkflowDataProxyAdditionalKeys)}
1170
+ */
1171
+ function getAdditionalKeys(additionalData) {
1172
+ const executionId = additionalData.executionId || __1.PLACEHOLDER_EMPTY_EXECUTION_ID;
1173
+ return {
1174
+ $executionId: executionId,
1175
+ $resumeWebhookUrl: `${additionalData.webhookWaitingBaseUrl}/${executionId}`,
1176
+ };
1177
+ }
1178
+ /**
1179
+ * Returns the requested decrypted credentials if the node has access to them.
1180
+ *
1181
+ * @export
1182
+ * @param {Workflow} workflow Workflow which requests the data
1183
+ * @param {INode} node Node which request the data
1184
+ * @param {string} type The credential type to return
1185
+ * @param {IWorkflowExecuteAdditionalData} additionalData
1186
+ * @returns {(ICredentialDataDecryptedObject | undefined)}
1187
+ */
1188
+ async function getCredentials(workflow, node, type, additionalData, mode, runExecutionData, runIndex, connectionInputData, itemIndex) {
1189
+ // Get the NodeType as it has the information if the credentials are required
1190
+ const nodeType = workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
1191
+ if (nodeType === undefined) {
1192
+ throw new workflow_1.NodeOperationError(node, `Node type "${node.type}" is not known so can not get credentials!`);
1193
+ }
1194
+ // Hardcode for now for security reasons that only a single node can access
1195
+ // all credentials
1196
+ const fullAccess = ["n8n-nodes-base.httpRequest"].includes(node.type);
1197
+ let nodeCredentialDescription;
1198
+ if (!fullAccess) {
1199
+ if (nodeType.description.credentials === undefined) {
1200
+ throw new workflow_1.NodeOperationError(node, `Node type "${node.type}" does not have any credentials defined!`);
1201
+ }
1202
+ nodeCredentialDescription = nodeType.description.credentials.find((credentialTypeDescription) => credentialTypeDescription.name === type);
1203
+ if (nodeCredentialDescription === undefined) {
1204
+ throw new workflow_1.NodeOperationError(node, `Node type "${node.type}" does not have any credentials of type "${type}" defined!`);
1205
+ }
1206
+ if (!workflow_1.NodeHelpers.displayParameter(additionalData.currentNodeParameters || node.parameters, nodeCredentialDescription, node.parameters)) {
1207
+ // Credentials should not be displayed so return undefined even if they would be defined
1208
+ return undefined;
1209
+ }
1210
+ }
1211
+ // Check if node has any credentials defined
1212
+ if (!fullAccess && (!node.credentials || !node.credentials[type])) {
1213
+ // If none are defined check if the credentials are required or not
1214
+ if (nodeCredentialDescription?.required === true) {
1215
+ // Credentials are required so error
1216
+ if (!node.credentials) {
1217
+ throw new workflow_1.NodeOperationError(node, "Node does not have any credentials set!");
1218
+ }
1219
+ if (!node.credentials[type]) {
1220
+ throw new workflow_1.NodeOperationError(node, `Node does not have any credentials set for "${type}"!`);
1221
+ }
1222
+ }
1223
+ else {
1224
+ // Credentials are not required so resolve with undefined
1225
+ return undefined;
1226
+ }
1227
+ }
1228
+ if (fullAccess && (!node.credentials || !node.credentials[type])) {
1229
+ // Make sure that fullAccess nodes still behave like before that if they
1230
+ // request access to credentials that are currently not set it returns undefined
1231
+ return undefined;
1232
+ }
1233
+ let expressionResolveValues;
1234
+ if (connectionInputData && runExecutionData && runIndex !== undefined) {
1235
+ expressionResolveValues = {
1236
+ connectionInputData,
1237
+ itemIndex: itemIndex || 0,
1238
+ node,
1239
+ runExecutionData,
1240
+ runIndex,
1241
+ workflow,
1242
+ };
1243
+ }
1244
+ const nodeCredentials = node.credentials
1245
+ ? node.credentials[type]
1246
+ : {};
1247
+ // TODO: solve using credentials via expression
1248
+ // if (name.charAt(0) === '=') {
1249
+ // // If the credential name is an expression resolve it
1250
+ // const additionalKeys = getAdditionalKeys(additionalData);
1251
+ // name = workflow.expression.getParameterValue(
1252
+ // name,
1253
+ // runExecutionData || null,
1254
+ // runIndex || 0,
1255
+ // itemIndex || 0,
1256
+ // node.name,
1257
+ // connectionInputData || [],
1258
+ // mode,
1259
+ // additionalKeys,
1260
+ // ) as string;
1261
+ // }
1262
+ const decryptedDataObject = await additionalData.credentialsHelper.getDecrypted(nodeCredentials, type, mode, false, expressionResolveValues);
1263
+ return decryptedDataObject;
1264
+ }
1265
+ /**
1266
+ * Returns a copy of the node
1267
+ *
1268
+ * @export
1269
+ * @param {INode} node
1270
+ * @returns {INode}
1271
+ */
1272
+ function getNode(node) {
1273
+ return JSON.parse(JSON.stringify(node));
1274
+ }
1275
+ /**
1276
+ * Returns the requested resolved (all expressions replaced) node parameters.
1277
+ *
1278
+ * @export
1279
+ * @param {Workflow} workflow
1280
+ * @param {(IRunExecutionData | null)} runExecutionData
1281
+ * @param {number} runIndex
1282
+ * @param {INodeExecutionData[]} connectionInputData
1283
+ * @param {INode} node
1284
+ * @param {string} parameterName
1285
+ * @param {number} itemIndex
1286
+ * @param {*} [fallbackValue]
1287
+ * @returns {(NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object)}
1288
+ */
1289
+ function getNodeParameter(workflow, runExecutionData, runIndex, connectionInputData, node, parameterName, itemIndex, mode, additionalKeys, fallbackValue) {
1290
+ const nodeType = workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
1291
+ if (nodeType === undefined) {
1292
+ throw new Error(`Node type "${node.type}" is not known so can not return paramter value!`);
1293
+ }
1294
+ const value = (0, lodash_1.get)(node.parameters, parameterName, fallbackValue);
1295
+ if (value === undefined) {
1296
+ throw new Error(`Could not get parameter "${parameterName}"!`);
1297
+ }
1298
+ let returnData;
1299
+ try {
1300
+ returnData = workflow.expression.getParameterValue(value, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, mode, additionalKeys);
1301
+ }
1302
+ catch (e) {
1303
+ if (e instanceof Error) {
1304
+ e.message += ` [Error in parameter: "${parameterName}"]`;
1305
+ }
1306
+ throw e;
1307
+ }
1308
+ return returnData;
1309
+ }
1310
+ /**
1311
+ * Returns if execution should be continued even if there was an error.
1312
+ *
1313
+ * @export
1314
+ * @param {INode} node
1315
+ * @returns {boolean}
1316
+ */
1317
+ function continueOnFail(node) {
1318
+ return (0, lodash_1.get)(node, "continueOnFail", false);
1319
+ }
1320
+ /**
1321
+ * Returns the webhook URL of the webhook with the given name
1322
+ *
1323
+ * @export
1324
+ * @param {string} name
1325
+ * @param {Workflow} workflow
1326
+ * @param {INode} node
1327
+ * @param {IWorkflowExecuteAdditionalData} additionalData
1328
+ * @param {boolean} [isTest]
1329
+ * @returns {(string | undefined)}
1330
+ */
1331
+ function getNodeWebhookUrl(name, workflow, node, additionalData, mode, additionalKeys, isTest) {
1332
+ let baseUrl = additionalData.webhookBaseUrl;
1333
+ if (isTest === true) {
1334
+ baseUrl = additionalData.webhookTestBaseUrl;
1335
+ }
1336
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
1337
+ const webhookDescription = getWebhookDescription(name, workflow, node);
1338
+ if (webhookDescription === undefined) {
1339
+ return undefined;
1340
+ }
1341
+ const path = workflow.expression.getSimpleParameterValue(node, webhookDescription.path, mode, additionalKeys);
1342
+ if (path === undefined) {
1343
+ return undefined;
1344
+ }
1345
+ const isFullPath = workflow.expression.getSimpleParameterValue(node, webhookDescription.isFullPath, mode, additionalKeys, false);
1346
+ return workflow_1.NodeHelpers.getNodeWebhookUrl(baseUrl, workflow.id, node, path.toString(), isFullPath);
1347
+ }
1348
+ /**
1349
+ * Returns the timezone for the workflow
1350
+ *
1351
+ * @export
1352
+ * @param {Workflow} workflow
1353
+ * @param {IWorkflowExecuteAdditionalData} additionalData
1354
+ * @returns {string}
1355
+ */
1356
+ function getTimezone(workflow, additionalData) {
1357
+ // eslint-disable-next-line @typescript-eslint/prefer-optional-chain
1358
+ if (workflow.settings !== undefined &&
1359
+ workflow.settings["timezone"] !== undefined) {
1360
+ return workflow.settings.timezone;
1361
+ }
1362
+ return additionalData.timezone;
1363
+ }
1364
+ /**
1365
+ * Returns the full webhook description of the webhook with the given name
1366
+ *
1367
+ * @export
1368
+ * @param {string} name
1369
+ * @param {Workflow} workflow
1370
+ * @param {INode} node
1371
+ * @returns {(IWebhookDescription | undefined)}
1372
+ */
1373
+ function getWebhookDescription(name, workflow, node) {
1374
+ const nodeType = workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
1375
+ if (nodeType.description.webhooks === undefined) {
1376
+ // Node does not have any webhooks so return
1377
+ return undefined;
1378
+ }
1379
+ // eslint-disable-next-line no-restricted-syntax
1380
+ for (const webhookDescription of nodeType.description.webhooks) {
1381
+ if (webhookDescription.name === name) {
1382
+ return webhookDescription;
1383
+ }
1384
+ }
1385
+ return undefined;
1386
+ }
1387
+ /**
1388
+ * Returns the workflow metadata
1389
+ *
1390
+ * @export
1391
+ * @param {Workflow} workflow
1392
+ * @returns {IWorkflowMetadata}
1393
+ */
1394
+ function getWorkflowMetadata(workflow) {
1395
+ return {
1396
+ id: workflow.id,
1397
+ name: workflow.name,
1398
+ active: workflow.active,
1399
+ };
1400
+ }
1401
+ /**
1402
+ * Returns the execute functions the poll nodes have access to.
1403
+ *
1404
+ * @export
1405
+ * @param {Workflow} workflow
1406
+ * @param {INode} node
1407
+ * @param {IWorkflowExecuteAdditionalData} additionalData
1408
+ * @param {WorkflowExecuteMode} mode
1409
+ * @returns {ITriggerFunctions}
1410
+ */
1411
+ // TODO: Check if I can get rid of: additionalData, and so then maybe also at ActiveWorkflowRunner.add
1412
+ function getExecutePollFunctions(workflow, node, additionalData, mode, activation) {
1413
+ return ((workflow, node) => {
1414
+ return {
1415
+ __emit: (_data) => {
1416
+ throw new Error("Overwrite NodeExecuteFunctions.getExecutePullFunctions.__emit function!");
1417
+ },
1418
+ async getCredentials(type) {
1419
+ return getCredentials(workflow, node, type, additionalData, mode);
1420
+ },
1421
+ getMode: () => {
1422
+ return mode;
1423
+ },
1424
+ getActivationMode: () => {
1425
+ return activation;
1426
+ },
1427
+ getNode: () => {
1428
+ return getNode(node);
1429
+ },
1430
+ getNodeParameter: (parameterName, fallbackValue) => {
1431
+ const runExecutionData = null;
1432
+ const itemIndex = 0;
1433
+ const runIndex = 0;
1434
+ const connectionInputData = [];
1435
+ return getNodeParameter(workflow, runExecutionData, runIndex, connectionInputData, node, parameterName, itemIndex, mode, getAdditionalKeys(additionalData), fallbackValue);
1436
+ },
1437
+ getRestApiUrl: () => {
1438
+ return additionalData.restApiUrl;
1439
+ },
1440
+ getTimezone: () => {
1441
+ return getTimezone(workflow, additionalData);
1442
+ },
1443
+ getWorkflow: () => {
1444
+ return getWorkflowMetadata(workflow);
1445
+ },
1446
+ getWorkflowStaticData(type) {
1447
+ return workflow.getStaticData(type, node);
1448
+ },
1449
+ helpers: {
1450
+ httpRequest,
1451
+ async prepareBinaryData(binaryData, filePath, mimeType) {
1452
+ return prepareBinaryData.call(this, binaryData, additionalData.executionId, filePath, mimeType);
1453
+ },
1454
+ request: proxyRequestToAxios,
1455
+ async requestWithAuthentication(credentialsType, requestOptions, additionalCredentialOptions) {
1456
+ return requestWithAuthentication.call(this, credentialsType, requestOptions, workflow, node, additionalData, additionalCredentialOptions);
1457
+ },
1458
+ async requestOAuth2(credentialsType, requestOptions, oAuth2Options) {
1459
+ return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, oAuth2Options);
1460
+ },
1461
+ async getCurrentOAuth2AccessToken(credentialsType, oAuth2Options) {
1462
+ return getCurrentOAuth2AccessToken.call(this, credentialsType, node, additionalData, oAuth2Options);
1463
+ },
1464
+ async requestOAuth1(credentialsType, requestOptions) {
1465
+ return requestOAuth1.call(this, credentialsType, requestOptions);
1466
+ },
1467
+ async httpRequestWithAuthentication(credentialsType, requestOptions, additionalCredentialOptions) {
1468
+ return httpRequestWithAuthentication.call(this, credentialsType, requestOptions, workflow, node, additionalData, additionalCredentialOptions);
1469
+ },
1470
+ returnJsonArray,
1471
+ },
1472
+ };
1473
+ })(workflow, node);
1474
+ }
1475
+ /**
1476
+ * Returns the execute functions the trigger nodes have access to.
1477
+ *
1478
+ * @export
1479
+ * @param {Workflow} workflow
1480
+ * @param {INode} node
1481
+ * @param {IWorkflowExecuteAdditionalData} additionalData
1482
+ * @param {WorkflowExecuteMode} mode
1483
+ * @returns {ITriggerFunctions}
1484
+ */
1485
+ // TODO: Check if I can get rid of: additionalData, and so then maybe also at ActiveWorkflowRunner.add
1486
+ function getExecuteTriggerFunctions(workflow, node, additionalData, mode, activation) {
1487
+ return ((workflow, node) => {
1488
+ return {
1489
+ emit: (_data) => {
1490
+ throw new Error("Overwrite NodeExecuteFunctions.getExecuteTriggerFunctions.emit function!");
1491
+ },
1492
+ async getCredentials(type) {
1493
+ return getCredentials(workflow, node, type, additionalData, mode);
1494
+ },
1495
+ getNode: () => {
1496
+ return getNode(node);
1497
+ },
1498
+ getMode: () => {
1499
+ return mode;
1500
+ },
1501
+ getActivationMode: () => {
1502
+ return activation;
1503
+ },
1504
+ getNodeParameter: (parameterName, fallbackValue) => {
1505
+ const runExecutionData = null;
1506
+ const itemIndex = 0;
1507
+ const runIndex = 0;
1508
+ const connectionInputData = [];
1509
+ return getNodeParameter(workflow, runExecutionData, runIndex, connectionInputData, node, parameterName, itemIndex, mode, getAdditionalKeys(additionalData), fallbackValue);
1510
+ },
1511
+ getRestApiUrl: () => {
1512
+ return additionalData.restApiUrl;
1513
+ },
1514
+ getTimezone: () => {
1515
+ return getTimezone(workflow, additionalData);
1516
+ },
1517
+ getWorkflow: () => {
1518
+ return getWorkflowMetadata(workflow);
1519
+ },
1520
+ getWorkflowStaticData(type) {
1521
+ return workflow.getStaticData(type, node);
1522
+ },
1523
+ helpers: {
1524
+ httpRequest,
1525
+ async requestWithAuthentication(credentialsType, requestOptions, additionalCredentialOptions) {
1526
+ return requestWithAuthentication.call(this, credentialsType, requestOptions, workflow, node, additionalData, additionalCredentialOptions);
1527
+ },
1528
+ async prepareBinaryData(binaryData, filePath, mimeType) {
1529
+ return prepareBinaryData.call(this, binaryData, additionalData.executionId, filePath, mimeType);
1530
+ },
1531
+ request: proxyRequestToAxios,
1532
+ async requestOAuth2(credentialsType, requestOptions, oAuth2Options) {
1533
+ return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, oAuth2Options);
1534
+ },
1535
+ async requestOAuth1(credentialsType, requestOptions) {
1536
+ return requestOAuth1.call(this, credentialsType, requestOptions);
1537
+ },
1538
+ async httpRequestWithAuthentication(credentialsType, requestOptions, additionalCredentialOptions) {
1539
+ return httpRequestWithAuthentication.call(this, credentialsType, requestOptions, workflow, node, additionalData, additionalCredentialOptions);
1540
+ },
1541
+ returnJsonArray,
1542
+ },
1543
+ };
1544
+ })(workflow, node);
1545
+ }
1546
+ /**
1547
+ * Returns the execute functions regular nodes have access to.
1548
+ *
1549
+ * @export
1550
+ * @param {Workflow} workflow
1551
+ * @param {IRunExecutionData} runExecutionData
1552
+ * @param {number} runIndex
1553
+ * @param {INodeExecutionData[]} connectionInputData
1554
+ * @param {ITaskDataConnections} inputData
1555
+ * @param {INode} node
1556
+ * @param {IWorkflowExecuteAdditionalData} additionalData
1557
+ * @param {WorkflowExecuteMode} mode
1558
+ * @returns {IExecuteFunctions}
1559
+ */
1560
+ function getExecuteFunctions(workflow, runExecutionData, runIndex, connectionInputData, inputData, node, additionalData, mode, nodeTypeData, closeFunctions) {
1561
+ return ((workflow, _runExecutionData, connectionInputData, inputData, node, nodeTypeData, closeFunctions) => {
1562
+ let runExecutionData = _runExecutionData;
1563
+ return {
1564
+ continueOnFail: () => {
1565
+ return continueOnFail(node);
1566
+ },
1567
+ evaluateExpression: (expression, itemIndex) => {
1568
+ return workflow.expression.resolveSimpleParameterValue(`=${expression}`, {}, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, mode, getAdditionalKeys(additionalData));
1569
+ },
1570
+ async executeWorkflow(workflowInfo, inputData) {
1571
+ return additionalData
1572
+ .executeWorkflow(workflowInfo, additionalData, inputData)
1573
+ .then(async (result) => BinaryDataManager_1.BinaryDataManager.getInstance().duplicateBinaryData(result, additionalData.executionId));
1574
+ },
1575
+ getContext(type) {
1576
+ return workflow_1.NodeHelpers.getContext(runExecutionData, type, node);
1577
+ },
1578
+ async getCredentials(type, itemIndex) {
1579
+ return getCredentials(workflow, node, type, additionalData, mode, runExecutionData, runIndex, connectionInputData, itemIndex);
1580
+ },
1581
+ getExecutionId: () => {
1582
+ return additionalData.executionId;
1583
+ },
1584
+ getInputData: (inputIndex = 0, inputName = "main") => {
1585
+ if (!Object.hasOwn(inputData, inputName)) {
1586
+ // Return empty array because else it would throw error when nothing is connected to input
1587
+ return [];
1588
+ }
1589
+ // TODO: Check if nodeType has input with that index defined
1590
+ if (inputData[inputName].length < inputIndex) {
1591
+ throw new Error(`Could not get input index "${inputIndex}" of input "${inputName}"!`);
1592
+ }
1593
+ if (inputData[inputName][inputIndex] === null) {
1594
+ // return [];
1595
+ throw new Error(`Value "${inputIndex}" of input "${inputName}" did not get set!`);
1596
+ }
1597
+ return inputData[inputName][inputIndex];
1598
+ },
1599
+ getNodeParameter: (parameterName, itemIndex, fallbackValue) => {
1600
+ return getNodeParameter(workflow, runExecutionData, runIndex, connectionInputData, node, parameterName, itemIndex, mode, getAdditionalKeys(additionalData), fallbackValue);
1601
+ },
1602
+ getMode: () => {
1603
+ return mode;
1604
+ },
1605
+ getNode: () => {
1606
+ return getNode(node);
1607
+ },
1608
+ getRestApiUrl: () => {
1609
+ return additionalData.restApiUrl;
1610
+ },
1611
+ getTimezone: () => {
1612
+ return getTimezone(workflow, additionalData);
1613
+ },
1614
+ getWorkflow: () => {
1615
+ return getWorkflowMetadata(workflow);
1616
+ },
1617
+ getWorkflowDataProxy: (itemIndex) => {
1618
+ const dataProxy = new workflow_1.WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, {}, mode, getAdditionalKeys(additionalData));
1619
+ return dataProxy.getDataProxy();
1620
+ },
1621
+ getWorkflowStaticData(type) {
1622
+ return workflow.getStaticData(type, node);
1623
+ },
1624
+ prepareOutputData: workflow_1.NodeHelpers.prepareOutputData,
1625
+ async putExecutionToWait(waitTill) {
1626
+ runExecutionData.waitTill = waitTill;
1627
+ },
1628
+ sendMessageToUI(...args) {
1629
+ if (mode !== "manual") {
1630
+ return;
1631
+ }
1632
+ try {
1633
+ if (additionalData.sendMessageToUI) {
1634
+ additionalData.sendMessageToUI(node.name, args);
1635
+ }
1636
+ }
1637
+ catch (error) {
1638
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
1639
+ workflow_1.LoggerProxy.warn(`There was a problem sending messsage to UI: ${error instanceof Error ? error.message : String(error)}`);
1640
+ }
1641
+ },
1642
+ async sendResponse(response) {
1643
+ await additionalData.hooks?.executeHookFunctions("sendResponse", [
1644
+ response,
1645
+ ]);
1646
+ },
1647
+ helpers: {
1648
+ httpRequest,
1649
+ async requestWithAuthentication(credentialsType, requestOptions, additionalCredentialOptions) {
1650
+ return requestWithAuthentication.call(this, credentialsType, requestOptions, workflow, node, additionalData, additionalCredentialOptions);
1651
+ },
1652
+ assertBinaryData(itemIndex, propertyName, inputIndex = 0) {
1653
+ const binaryKeyData = inputData["main"][inputIndex]?.[itemIndex].binary;
1654
+ if (binaryKeyData === undefined) {
1655
+ throw new workflow_1.NodeOperationError(node, `This operation expects the node's input data to contain a binary file '${propertyName}', but none was found [item ${itemIndex}]`, {
1656
+ itemIndex,
1657
+ description: "Make sure that the previous node outputs a binary file",
1658
+ });
1659
+ }
1660
+ const binaryPropertyData = binaryKeyData[propertyName];
1661
+ if (binaryPropertyData === undefined) {
1662
+ throw new workflow_1.NodeOperationError(node, `The item has no binary field '${propertyName}' [item ${itemIndex}]`, {
1663
+ itemIndex,
1664
+ description: "Check that the parameter where you specified the input binary field name is correct, and that it matches a field in the binary input",
1665
+ });
1666
+ }
1667
+ return binaryPropertyData;
1668
+ },
1669
+ async prepareBinaryData(binaryData, filePath, mimeType) {
1670
+ return prepareBinaryData.call(this, binaryData, additionalData.executionId, filePath, mimeType);
1671
+ },
1672
+ async getBinaryDataBuffer(itemIndex, propertyName, inputIndex = 0) {
1673
+ return getBinaryDataBuffer.call(this, inputData, itemIndex, propertyName, inputIndex);
1674
+ },
1675
+ request: proxyRequestToAxios,
1676
+ async requestOAuth2(credentialsType, requestOptions, oAuth2Options) {
1677
+ return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, oAuth2Options);
1678
+ },
1679
+ async requestOAuth1(credentialsType, requestOptions) {
1680
+ return requestOAuth1.call(this, credentialsType, requestOptions);
1681
+ },
1682
+ async httpRequestWithAuthentication(credentialsType, requestOptions, additionalCredentialOptions) {
1683
+ return httpRequestWithAuthentication.call(this, credentialsType, requestOptions, workflow, node, additionalData, additionalCredentialOptions);
1684
+ },
1685
+ returnJsonArray,
1686
+ normalizeItems,
1687
+ },
1688
+ getInstanceId: () => {
1689
+ // Return the node's instance ID if available, otherwise return an empty string
1690
+ return node.id || "";
1691
+ },
1692
+ async getInputConnectionData(connectionType, itemIndex) {
1693
+ return await getInputConnectionData.call(this, workflow, runExecutionData, runIndex, connectionInputData, inputData, additionalData, connectionType, mode, itemIndex, nodeTypeData, closeFunctions);
1694
+ },
1695
+ addInputData(data, node) {
1696
+ const nodeName = node.name;
1697
+ const currentNodeRunIndex = 0;
1698
+ runExecutionData = {
1699
+ startData: {
1700
+ destinationNode: nodeName,
1701
+ },
1702
+ resultData: {
1703
+ runData: {
1704
+ [nodeName]: [JSON.parse(JSON.stringify(data))],
1705
+ },
1706
+ lastNodeExecuted: nodeName,
1707
+ },
1708
+ };
1709
+ return {
1710
+ index: currentNodeRunIndex,
1711
+ inputExecutionData: runExecutionData,
1712
+ };
1713
+ },
1714
+ addOutputData(data, node) {
1715
+ const nodeName = node.name;
1716
+ const outputExecutionData = {
1717
+ startData: {
1718
+ destinationNode: nodeName,
1719
+ },
1720
+ resultData: {
1721
+ error: typeof data === "object" && "error" in data
1722
+ ? data.error
1723
+ : undefined,
1724
+ runData: {
1725
+ [nodeName]: [JSON.parse(JSON.stringify(data))],
1726
+ },
1727
+ lastNodeExecuted: nodeName,
1728
+ },
1729
+ };
1730
+ return { outputExecutionData };
1731
+ },
1732
+ getExecutionCancelSignal: () => {
1733
+ return undefined;
1734
+ },
1735
+ };
1736
+ })(workflow, runExecutionData, connectionInputData, inputData, node, nodeTypeData, closeFunctions);
1737
+ }
1738
+ /**
1739
+ * Returns the execute functions regular nodes have access to when single-function is defined.
1740
+ *
1741
+ * @export
1742
+ * @param {Workflow} workflow
1743
+ * @param {IRunExecutionData} runExecutionData
1744
+ * @param {number} runIndex
1745
+ * @param {INodeExecutionData[]} connectionInputData
1746
+ * @param {ITaskDataConnections} inputData
1747
+ * @param {INode} node
1748
+ * @param {number} itemIndex
1749
+ * @param {IWorkflowExecuteAdditionalData} additionalData
1750
+ * @param {WorkflowExecuteMode} mode
1751
+ * @returns {IExecuteSingleFunctions}
1752
+ */
1753
+ function getExecuteSingleFunctions(workflow, runExecutionData, runIndex, connectionInputData, inputData, node, itemIndex, additionalData, mode) {
1754
+ return ((workflow, runExecutionData, connectionInputData, inputData, node, itemIndex) => {
1755
+ return {
1756
+ continueOnFail: () => {
1757
+ return continueOnFail(node);
1758
+ },
1759
+ evaluateExpression: (expression, evaluateItemIndex) => {
1760
+ const resolvedItemIndex = evaluateItemIndex === undefined ? itemIndex : evaluateItemIndex;
1761
+ return workflow.expression.resolveSimpleParameterValue(`=${expression}`, {}, runExecutionData, runIndex, resolvedItemIndex, node.name, connectionInputData, mode, getAdditionalKeys(additionalData));
1762
+ },
1763
+ getContext(type) {
1764
+ return workflow_1.NodeHelpers.getContext(runExecutionData, type, node);
1765
+ },
1766
+ async getCredentials(type) {
1767
+ return getCredentials(workflow, node, type, additionalData, mode, runExecutionData, runIndex, connectionInputData, itemIndex);
1768
+ },
1769
+ getInputData: (inputIndex = 0, inputName = "main") => {
1770
+ if (!Object.hasOwn(inputData, inputName)) {
1771
+ // Return empty array because else it would throw error when nothing is connected to input
1772
+ return { json: {} };
1773
+ }
1774
+ // TODO: Check if nodeType has input with that index defined
1775
+ if (inputData[inputName].length < inputIndex) {
1776
+ throw new Error(`Could not get input index "${inputIndex}" of input "${inputName}"!`);
1777
+ }
1778
+ const allItems = inputData[inputName][inputIndex];
1779
+ if (allItems === null) {
1780
+ // return [];
1781
+ throw new Error(`Value "${inputIndex}" of input "${inputName}" did not get set!`);
1782
+ }
1783
+ if (allItems[itemIndex] === null) {
1784
+ // return [];
1785
+ throw new Error(`Value "${inputIndex}" of input "${inputName}" with itemIndex "${itemIndex}" did not get set!`);
1786
+ }
1787
+ return allItems[itemIndex];
1788
+ },
1789
+ getMode: () => {
1790
+ return mode;
1791
+ },
1792
+ getNode: () => {
1793
+ return getNode(node);
1794
+ },
1795
+ getRestApiUrl: () => {
1796
+ return additionalData.restApiUrl;
1797
+ },
1798
+ getTimezone: () => {
1799
+ return getTimezone(workflow, additionalData);
1800
+ },
1801
+ getNodeParameter: (parameterName, fallbackValue) => {
1802
+ return getNodeParameter(workflow, runExecutionData, runIndex, connectionInputData, node, parameterName, itemIndex, mode, getAdditionalKeys(additionalData), fallbackValue);
1803
+ },
1804
+ getWorkflow: () => {
1805
+ return getWorkflowMetadata(workflow);
1806
+ },
1807
+ getWorkflowDataProxy: () => {
1808
+ const dataProxy = new workflow_1.WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, {}, mode, getAdditionalKeys(additionalData));
1809
+ return dataProxy.getDataProxy();
1810
+ },
1811
+ getWorkflowStaticData(type) {
1812
+ return workflow.getStaticData(type, node);
1813
+ },
1814
+ helpers: {
1815
+ httpRequest,
1816
+ async requestWithAuthentication(credentialsType, requestOptions, additionalCredentialOptions) {
1817
+ return requestWithAuthentication.call(this, credentialsType, requestOptions, workflow, node, additionalData, additionalCredentialOptions);
1818
+ },
1819
+ async prepareBinaryData(binaryData, filePath, mimeType) {
1820
+ return prepareBinaryData.call(this, binaryData, additionalData.executionId, filePath, mimeType);
1821
+ },
1822
+ request: proxyRequestToAxios,
1823
+ async requestOAuth2(credentialsType, requestOptions, oAuth2Options) {
1824
+ return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, oAuth2Options);
1825
+ },
1826
+ async requestOAuth1(credentialsType, requestOptions) {
1827
+ return requestOAuth1.call(this, credentialsType, requestOptions);
1828
+ },
1829
+ async httpRequestWithAuthentication(credentialsType, requestOptions, additionalCredentialOptions) {
1830
+ return httpRequestWithAuthentication.call(this, credentialsType, requestOptions, workflow, node, additionalData, additionalCredentialOptions);
1831
+ },
1832
+ },
1833
+ getInstanceId: () => {
1834
+ // Return the node's instance ID if available, otherwise return an empty string
1835
+ return node.id || "";
1836
+ },
1837
+ };
1838
+ })(workflow, runExecutionData, connectionInputData, inputData, node, itemIndex);
1839
+ }
1840
+ function getCredentialTestFunctions() {
1841
+ return {
1842
+ helpers: {
1843
+ request: proxyRequestToAxios,
1844
+ },
1845
+ };
1846
+ }
1847
+ /**
1848
+ * Returns the execute functions regular nodes have access to in load-options-function.
1849
+ *
1850
+ * @export
1851
+ * @param {Workflow} workflow
1852
+ * @param {INode} node
1853
+ * @param {IWorkflowExecuteAdditionalData} additionalData
1854
+ * @returns {ILoadOptionsFunctions}
1855
+ */
1856
+ function getLoadOptionsFunctions(workflow, node, path, additionalData) {
1857
+ return ((workflow, node, path) => {
1858
+ const that = {
1859
+ async getCredentials(type) {
1860
+ return getCredentials(workflow, node, type, additionalData, "internal");
1861
+ },
1862
+ getCurrentNodeParameter: (parameterPath) => {
1863
+ const nodeParameters = additionalData.currentNodeParameters;
1864
+ const resolvedPath = parameterPath.charAt(0) === "&"
1865
+ ? `${path.split(".").slice(1, -1).join(".")}.${parameterPath.slice(1)}`
1866
+ : parameterPath;
1867
+ return (0, lodash_1.get)(nodeParameters, resolvedPath);
1868
+ },
1869
+ getCurrentNodeParameters: () => {
1870
+ return additionalData.currentNodeParameters;
1871
+ },
1872
+ getNode: () => {
1873
+ return getNode(node);
1874
+ },
1875
+ getNodeParameter: (parameterName, fallbackValue) => {
1876
+ const runExecutionData = null;
1877
+ const itemIndex = 0;
1878
+ const runIndex = 0;
1879
+ const connectionInputData = [];
1880
+ return getNodeParameter(workflow, runExecutionData, runIndex, connectionInputData, node, parameterName, itemIndex, "internal", getAdditionalKeys(additionalData), fallbackValue);
1881
+ },
1882
+ getTimezone: () => {
1883
+ return getTimezone(workflow, additionalData);
1884
+ },
1885
+ getRestApiUrl: () => {
1886
+ return additionalData.restApiUrl;
1887
+ },
1888
+ helpers: {
1889
+ httpRequest,
1890
+ async requestWithAuthentication(credentialsType, requestOptions, additionalCredentialOptions) {
1891
+ return requestWithAuthentication.call(this, credentialsType, requestOptions, workflow, node, additionalData, additionalCredentialOptions);
1892
+ },
1893
+ request: proxyRequestToAxios,
1894
+ async requestOAuth2(credentialsType, requestOptions, oAuth2Options) {
1895
+ return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, oAuth2Options);
1896
+ },
1897
+ async requestOAuth1(credentialsType, requestOptions) {
1898
+ return requestOAuth1.call(this, credentialsType, requestOptions);
1899
+ },
1900
+ async httpRequestWithAuthentication(credentialsType, requestOptions, additionalCredentialOptions) {
1901
+ return httpRequestWithAuthentication.call(this, credentialsType, requestOptions, workflow, node, additionalData, additionalCredentialOptions);
1902
+ },
1903
+ },
1904
+ getInstanceId: () => {
1905
+ // Return the node's instance ID if available, otherwise return an empty string
1906
+ return node.id || "";
1907
+ },
1908
+ };
1909
+ return that;
1910
+ })(workflow, node, path);
1911
+ }
1912
+ /**
1913
+ * Returns the execute functions regular nodes have access to in hook-function.
1914
+ *
1915
+ * @export
1916
+ * @param {Workflow} workflow
1917
+ * @param {INode} node
1918
+ * @param {IWorkflowExecuteAdditionalData} additionalData
1919
+ * @param {WorkflowExecuteMode} mode
1920
+ * @returns {IHookFunctions}
1921
+ */
1922
+ function getExecuteHookFunctions(workflow, node, additionalData, mode, activation, isTest, webhookData) {
1923
+ return ((workflow, node) => {
1924
+ const that = {
1925
+ async getCredentials(type) {
1926
+ return getCredentials(workflow, node, type, additionalData, mode);
1927
+ },
1928
+ getMode: () => {
1929
+ return mode;
1930
+ },
1931
+ getActivationMode: () => {
1932
+ return activation;
1933
+ },
1934
+ getNode: () => {
1935
+ return getNode(node);
1936
+ },
1937
+ getNodeParameter: (parameterName, fallbackValue) => {
1938
+ const runExecutionData = null;
1939
+ const itemIndex = 0;
1940
+ const runIndex = 0;
1941
+ const connectionInputData = [];
1942
+ return getNodeParameter(workflow, runExecutionData, runIndex, connectionInputData, node, parameterName, itemIndex, mode, getAdditionalKeys(additionalData), fallbackValue);
1943
+ },
1944
+ getNodeWebhookUrl: (name) => {
1945
+ return getNodeWebhookUrl(name, workflow, node, additionalData, mode, getAdditionalKeys(additionalData), isTest);
1946
+ },
1947
+ getTimezone: () => {
1948
+ return getTimezone(workflow, additionalData);
1949
+ },
1950
+ getWebhookName() {
1951
+ if (webhookData === undefined) {
1952
+ throw new Error("Is only supported in webhook functions!");
1953
+ }
1954
+ return webhookData.webhookDescription.name;
1955
+ },
1956
+ getWebhookDescription(name) {
1957
+ return getWebhookDescription(name, workflow, node);
1958
+ },
1959
+ getWorkflow: () => {
1960
+ return getWorkflowMetadata(workflow);
1961
+ },
1962
+ getWorkflowStaticData(type) {
1963
+ return workflow.getStaticData(type, node);
1964
+ },
1965
+ helpers: {
1966
+ httpRequest,
1967
+ async requestWithAuthentication(credentialsType, requestOptions, additionalCredentialOptions) {
1968
+ return requestWithAuthentication.call(this, credentialsType, requestOptions, workflow, node, additionalData, additionalCredentialOptions);
1969
+ },
1970
+ request: proxyRequestToAxios,
1971
+ async requestOAuth2(credentialsType, requestOptions, oAuth2Options) {
1972
+ return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, oAuth2Options);
1973
+ },
1974
+ async requestOAuth1(credentialsType, requestOptions) {
1975
+ return requestOAuth1.call(this, credentialsType, requestOptions);
1976
+ },
1977
+ async httpRequestWithAuthentication(credentialsType, requestOptions, additionalCredentialOptions) {
1978
+ return httpRequestWithAuthentication.call(this, credentialsType, requestOptions, workflow, node, additionalData, additionalCredentialOptions);
1979
+ },
1980
+ },
1981
+ getInstanceId: () => {
1982
+ // Return the node's instance ID if available, otherwise return an empty string
1983
+ return node.id || "";
1984
+ },
1985
+ };
1986
+ return that;
1987
+ })(workflow, node);
1988
+ }
1989
+ /**
1990
+ * Returns the execute functions regular nodes have access to when webhook-function is defined.
1991
+ *
1992
+ * @export
1993
+ * @param {Workflow} workflow
1994
+ * @param {IRunExecutionData} runExecutionData
1995
+ * @param {INode} node
1996
+ * @param {IWorkflowExecuteAdditionalData} additionalData
1997
+ * @param {WorkflowExecuteMode} mode
1998
+ * @returns {IWebhookFunctions}
1999
+ */
2000
+ function getExecuteWebhookFunctions(workflow, node, additionalData, mode, webhookData, initialRunExecutionData) {
2001
+ let runExecutionData = initialRunExecutionData;
2002
+ return ((workflow, node) => {
2003
+ return {
2004
+ logger: workflow_1.LoggerProxy,
2005
+ evaluateExpression: (_expression, _itemIndex) => {
2006
+ //TODO: Implement this EVALUATE EXPRESSION in get webhook execute function
2007
+ return {};
2008
+ },
2009
+ getChildNodes: (_nodeName, _options) => {
2010
+ return [];
2011
+ },
2012
+ getParentNodes: (_nodeName) => {
2013
+ return [];
2014
+ },
2015
+ getBodyData() {
2016
+ if (additionalData.httpRequest === undefined) {
2017
+ throw new Error("Request is missing!");
2018
+ }
2019
+ return additionalData.httpRequest.body;
2020
+ },
2021
+ async getCredentials(type) {
2022
+ return getCredentials(workflow, node, type, additionalData, mode);
2023
+ },
2024
+ nodeHelpers: {
2025
+ copyBinaryFile: async (filePath, _fileName, mimeType) => {
2026
+ // Use fs.promises.readFile instead of fsReadFileAsync
2027
+ const buffer = await node_fs_1.promises.readFile(filePath);
2028
+ return prepareBinaryData(buffer, workflow.id || "", filePath, mimeType);
2029
+ },
2030
+ },
2031
+ getHeaderData() {
2032
+ if (additionalData.httpRequest === undefined) {
2033
+ throw new Error("Request is missing!");
2034
+ }
2035
+ return additionalData.httpRequest.headers;
2036
+ },
2037
+ getMode: () => {
2038
+ return mode;
2039
+ },
2040
+ getNode: () => {
2041
+ return getNode(node);
2042
+ },
2043
+ getNodeParameter: (parameterName, fallbackValue, _options) => {
2044
+ const itemIndex = 0;
2045
+ const runIndex = 0;
2046
+ let connectionInputData = [];
2047
+ let executionData;
2048
+ if (runExecutionData?.executionData !== undefined) {
2049
+ executionData = runExecutionData.executionData.nodeExecutionStack[0];
2050
+ if (executionData !== undefined) {
2051
+ connectionInputData = executionData.data["main"]?.[0] || [];
2052
+ }
2053
+ }
2054
+ return getNodeParameter(workflow, runExecutionData || null, runIndex, connectionInputData, node, parameterName, itemIndex, mode, getAdditionalKeys(additionalData), fallbackValue);
2055
+ },
2056
+ getParamsData() {
2057
+ if (additionalData.httpRequest === undefined) {
2058
+ throw new Error("Request is missing!");
2059
+ }
2060
+ return additionalData.httpRequest.params;
2061
+ },
2062
+ getQueryData() {
2063
+ if (additionalData.httpRequest === undefined) {
2064
+ throw new Error("Request is missing!");
2065
+ }
2066
+ return additionalData.httpRequest.query;
2067
+ },
2068
+ getRequestObject() {
2069
+ if (additionalData.httpRequest === undefined) {
2070
+ throw new Error("Request is missing!");
2071
+ }
2072
+ return additionalData.httpRequest;
2073
+ },
2074
+ getResponseObject() {
2075
+ if (additionalData.httpResponse === undefined) {
2076
+ throw new Error("Response is missing!");
2077
+ }
2078
+ return additionalData.httpResponse;
2079
+ },
2080
+ getNodeWebhookUrl: (name) => {
2081
+ return getNodeWebhookUrl(name, workflow, node, additionalData, mode, getAdditionalKeys(additionalData));
2082
+ },
2083
+ getTimezone: () => {
2084
+ return getTimezone(workflow, additionalData);
2085
+ },
2086
+ getWorkflow: () => {
2087
+ return getWorkflowMetadata(workflow);
2088
+ },
2089
+ getWorkflowStaticData(type) {
2090
+ return workflow.getStaticData(type, node);
2091
+ },
2092
+ getWebhookName() {
2093
+ return webhookData.webhookDescription.name;
2094
+ },
2095
+ addInputData(data, node) {
2096
+ const nodeName = node.name;
2097
+ const currentNodeRunIndex = 0;
2098
+ runExecutionData = {
2099
+ startData: {
2100
+ destinationNode: nodeName,
2101
+ },
2102
+ resultData: {
2103
+ error: undefined,
2104
+ runData: {
2105
+ [nodeName]: [JSON.parse(JSON.stringify(data))],
2106
+ },
2107
+ lastNodeExecuted: nodeName,
2108
+ },
2109
+ executionData: {
2110
+ contextData: {},
2111
+ nodeExecutionStack: [],
2112
+ waitingExecution: {},
2113
+ },
2114
+ waitTill: undefined,
2115
+ };
2116
+ return {
2117
+ index: currentNodeRunIndex,
2118
+ inputExecutionData: runExecutionData,
2119
+ };
2120
+ },
2121
+ addOutputData(data, node) {
2122
+ const nodeName = node.name;
2123
+ const outputExecutionData = {
2124
+ startData: {
2125
+ destinationNode: nodeName,
2126
+ },
2127
+ resultData: {
2128
+ error: undefined,
2129
+ runData: {
2130
+ [nodeName]: [JSON.parse(JSON.stringify(data))],
2131
+ },
2132
+ lastNodeExecuted: nodeName,
2133
+ },
2134
+ executionData: {
2135
+ contextData: {},
2136
+ nodeExecutionStack: [],
2137
+ waitingExecution: {},
2138
+ },
2139
+ waitTill: undefined,
2140
+ };
2141
+ return { outputExecutionData };
2142
+ },
2143
+ logAiEvent: (_event, _data) => {
2144
+ // Simple implementation for logAiEvent in webhook functions
2145
+ return;
2146
+ },
2147
+ prepareOutputData: workflow_1.NodeHelpers.prepareOutputData,
2148
+ helpers: {
2149
+ httpRequest,
2150
+ async requestWithAuthentication(credentialsType, requestOptions, additionalCredentialOptions) {
2151
+ return requestWithAuthentication.call(this, credentialsType, requestOptions, workflow, node, additionalData, additionalCredentialOptions);
2152
+ },
2153
+ async prepareBinaryData(binaryData, filePath, mimeType) {
2154
+ return prepareBinaryData.call(this, binaryData, additionalData.executionId, filePath, mimeType);
2155
+ },
2156
+ request: proxyRequestToAxios,
2157
+ async requestOAuth2(credentialsType, requestOptions, oAuth2Options) {
2158
+ return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, oAuth2Options);
2159
+ },
2160
+ async requestOAuth1(credentialsType, requestOptions) {
2161
+ return requestOAuth1.call(this, credentialsType, requestOptions);
2162
+ },
2163
+ async httpRequestWithAuthentication(credentialsType, requestOptions, additionalCredentialOptions) {
2164
+ return httpRequestWithAuthentication.call(this, credentialsType, requestOptions, workflow, node, additionalData, additionalCredentialOptions);
2165
+ },
2166
+ returnJsonArray,
2167
+ },
2168
+ getInstanceId: () => {
2169
+ // Return the node's instance ID if available, otherwise return an empty string
2170
+ return node.id || "";
2171
+ },
2172
+ };
2173
+ })(workflow, node);
2174
+ }
2175
+ function getSupplyDataFunctions(workflow, runExecutionData, runIndex, connectionInputData, inputData, node, additionalData, mode, nodeTypeData, closeFunctions) {
2176
+ return ((workflow, _runExecutionData, connectionInputData, inputData, node, nodeTypeData, closeFunctions) => {
2177
+ let runExecutionData = _runExecutionData;
2178
+ return {
2179
+ logger: workflow_1.LoggerProxy,
2180
+ continueOnFail: () => {
2181
+ return continueOnFail(node);
2182
+ },
2183
+ evaluateExpression: (expression, itemIndex) => {
2184
+ return workflow.expression.resolveSimpleParameterValue(`=${expression}`, {}, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, mode, getAdditionalKeys(additionalData));
2185
+ },
2186
+ async executeWorkflow(workflowInfo, inputData) {
2187
+ return additionalData
2188
+ .executeWorkflow(workflowInfo, additionalData, inputData)
2189
+ .then(async (result) => BinaryDataManager_1.BinaryDataManager.getInstance().duplicateBinaryData(result, additionalData.executionId));
2190
+ },
2191
+ getContext(type) {
2192
+ return workflow_1.NodeHelpers.getContext(runExecutionData, type, node);
2193
+ },
2194
+ async getCredentials(type, itemIndex) {
2195
+ return getCredentials(workflow, node, type, additionalData, mode, runExecutionData, runIndex, connectionInputData, itemIndex);
2196
+ },
2197
+ getExecutionId: () => {
2198
+ return additionalData.executionId;
2199
+ },
2200
+ getInputData: (inputIndex = 0, inputName = "main") => {
2201
+ if (!Object.hasOwn(inputData, inputName)) {
2202
+ // Return empty array because else it would throw error when nothing is connected to input
2203
+ return [];
2204
+ }
2205
+ // TODO: Check if nodeType has input with that index defined
2206
+ if (inputData[inputName].length < inputIndex) {
2207
+ throw new Error(`Could not get input index "${inputIndex}" of input "${inputName}"!`);
2208
+ }
2209
+ if (inputData[inputName][inputIndex] === null) {
2210
+ // return [];
2211
+ throw new Error(`Value "${inputIndex}" of input "${inputName}" did not get set!`);
2212
+ }
2213
+ return inputData[inputName][inputIndex];
2214
+ },
2215
+ getNodeParameter: (parameterName, itemIndex, fallbackValue) => {
2216
+ return getNodeParameter(workflow, runExecutionData, runIndex, connectionInputData, node, parameterName, itemIndex, mode, getAdditionalKeys(additionalData), fallbackValue);
2217
+ },
2218
+ getMode: () => {
2219
+ return mode;
2220
+ },
2221
+ getNode: () => {
2222
+ return getNode(node);
2223
+ },
2224
+ getRestApiUrl: () => {
2225
+ return additionalData.restApiUrl;
2226
+ },
2227
+ getTimezone: () => {
2228
+ return getTimezone(workflow, additionalData);
2229
+ },
2230
+ getWorkflow: () => {
2231
+ return getWorkflowMetadata(workflow);
2232
+ },
2233
+ getWorkflowDataProxy: (itemIndex) => {
2234
+ const dataProxy = new workflow_1.WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, {}, mode, getAdditionalKeys(additionalData));
2235
+ return dataProxy.getDataProxy();
2236
+ },
2237
+ getWorkflowStaticData(type) {
2238
+ return workflow.getStaticData(type, node);
2239
+ },
2240
+ prepareOutputData: workflow_1.NodeHelpers.prepareOutputData,
2241
+ async putExecutionToWait(waitTill) {
2242
+ runExecutionData.waitTill = waitTill;
2243
+ },
2244
+ sendMessageToUI(...args) {
2245
+ if (mode !== "manual") {
2246
+ return;
2247
+ }
2248
+ try {
2249
+ if (additionalData.sendMessageToUI) {
2250
+ additionalData.sendMessageToUI(node.name, args);
2251
+ }
2252
+ }
2253
+ catch (error) {
2254
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
2255
+ workflow_1.LoggerProxy.warn(`There was a problem sending messsage to UI: ${error instanceof Error ? error.message : String(error)}`);
2256
+ }
2257
+ },
2258
+ async sendResponse(response) {
2259
+ await additionalData.hooks?.executeHookFunctions("sendResponse", [
2260
+ response,
2261
+ ]);
2262
+ },
2263
+ helpers: {
2264
+ httpRequest,
2265
+ async requestWithAuthentication(credentialsType, requestOptions, additionalCredentialOptions) {
2266
+ return requestWithAuthentication.call(this, credentialsType, requestOptions, workflow, node, additionalData, additionalCredentialOptions);
2267
+ },
2268
+ assertBinaryData(itemIndex, propertyName, inputIndex = 0) {
2269
+ const binaryKeyData = inputData["main"][inputIndex]?.[itemIndex].binary;
2270
+ if (binaryKeyData === undefined) {
2271
+ throw new workflow_1.NodeOperationError(node, `This operation expects the node's input data to contain a binary file '${propertyName}', but none was found [item ${itemIndex}]`, {
2272
+ itemIndex,
2273
+ description: "Make sure that the previous node outputs a binary file",
2274
+ });
2275
+ }
2276
+ const binaryPropertyData = binaryKeyData[propertyName];
2277
+ if (binaryPropertyData === undefined) {
2278
+ throw new workflow_1.NodeOperationError(node, `The item has no binary field '${propertyName}' [item ${itemIndex}]`, {
2279
+ itemIndex,
2280
+ description: "Check that the parameter where you specified the input binary field name is correct, and that it matches a field in the binary input",
2281
+ });
2282
+ }
2283
+ return binaryPropertyData;
2284
+ },
2285
+ async prepareBinaryData(binaryData, filePath, mimeType) {
2286
+ return prepareBinaryData.call(this, binaryData, additionalData.executionId, filePath, mimeType);
2287
+ },
2288
+ async getBinaryDataBuffer(itemIndex, propertyName, inputIndex = 0) {
2289
+ return getBinaryDataBuffer.call(this, inputData, itemIndex, propertyName, inputIndex);
2290
+ },
2291
+ request: proxyRequestToAxios,
2292
+ async requestOAuth2(credentialsType, requestOptions, oAuth2Options) {
2293
+ return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, oAuth2Options);
2294
+ },
2295
+ async requestOAuth1(credentialsType, requestOptions) {
2296
+ return requestOAuth1.call(this, credentialsType, requestOptions);
2297
+ },
2298
+ async httpRequestWithAuthentication(credentialsType, requestOptions, additionalCredentialOptions) {
2299
+ return httpRequestWithAuthentication.call(this, credentialsType, requestOptions, workflow, node, additionalData, additionalCredentialOptions);
2300
+ },
2301
+ returnJsonArray,
2302
+ normalizeItems,
2303
+ },
2304
+ getInstanceId: () => {
2305
+ // Return the node's instance ID if available, otherwise return an empty string
2306
+ return node.id || "";
2307
+ },
2308
+ addInputData(data, node) {
2309
+ const nodeName = node.name;
2310
+ const currentNodeRunIndex = 0;
2311
+ runExecutionData = {
2312
+ startData: {
2313
+ destinationNode: nodeName,
2314
+ },
2315
+ resultData: {
2316
+ error: undefined,
2317
+ runData: {
2318
+ [nodeName]: [JSON.parse(JSON.stringify(data))],
2319
+ },
2320
+ lastNodeExecuted: nodeName,
2321
+ },
2322
+ executionData: {
2323
+ contextData: {},
2324
+ nodeExecutionStack: [],
2325
+ waitingExecution: {},
2326
+ },
2327
+ waitTill: undefined,
2328
+ };
2329
+ return {
2330
+ index: currentNodeRunIndex,
2331
+ inputExecutionData: runExecutionData,
2332
+ };
2333
+ },
2334
+ addOutputData(data, node) {
2335
+ const nodeName = node.name;
2336
+ const outputExecutionData = {
2337
+ startData: {
2338
+ destinationNode: nodeName,
2339
+ },
2340
+ resultData: {
2341
+ error: undefined,
2342
+ runData: {
2343
+ [nodeName]: [JSON.parse(JSON.stringify(data))],
2344
+ },
2345
+ lastNodeExecuted: nodeName,
2346
+ },
2347
+ executionData: {
2348
+ contextData: {},
2349
+ nodeExecutionStack: [],
2350
+ waitingExecution: {},
2351
+ },
2352
+ waitTill: undefined,
2353
+ };
2354
+ return { outputExecutionData };
2355
+ },
2356
+ async getInputConnectionData(connectionType, itemIndex) {
2357
+ return await getInputConnectionData.call(this, workflow, runExecutionData, runIndex, connectionInputData, inputData, additionalData, connectionType, mode, itemIndex, nodeTypeData, closeFunctions);
2358
+ },
2359
+ };
2360
+ })(workflow, runExecutionData, connectionInputData, inputData, node, nodeTypeData, closeFunctions);
2361
+ }
2362
+ async function getInputConnectionData(workflow, runExecutionData, runIndex, connectionInputData, inputData, additionalData, connectionType, mode, itemIndex, nodeTypeData, closeFunctions) {
2363
+ const parentNode = this.getNode();
2364
+ const startTime = Date.now();
2365
+ const nodeInputs = getNodeInputs(workflow, parentNode, nodeTypeData.description).map((input) => typeof input === "string" ? { type: input } : input);
2366
+ const inputConfiguration = nodeInputs.find((input) => input.type === connectionType);
2367
+ if (inputConfiguration === undefined) {
2368
+ throw new workflow_1.NodeOperationError(parentNode, "Node does not have input of type", {
2369
+ description: `Node ${parentNode.name} does not have input of type ${connectionType}`,
2370
+ });
2371
+ }
2372
+ const connectedNodes = getConnectedNodes(workflow, parentNode, connectionType);
2373
+ if (connectedNodes.length === 0) {
2374
+ if (inputConfiguration.required) {
2375
+ throw new workflow_1.NodeOperationError(parentNode, `A ${inputConfiguration?.displayName ?? connectionType} sub-node must be connected and enabled`);
2376
+ }
2377
+ return inputConfiguration.maxConnections === 1 ? undefined : [];
2378
+ }
2379
+ if (inputConfiguration.maxConnections !== undefined &&
2380
+ connectedNodes.length > inputConfiguration.maxConnections) {
2381
+ throw new workflow_1.NodeOperationError(parentNode, `Only ${inputConfiguration.maxConnections} ${connectionType} sub-nodes are/is allowed to be connected`);
2382
+ }
2383
+ const nodes = [];
2384
+ for (const connectedNode of connectedNodes) {
2385
+ const connectedNodeType = workflow.nodeTypes.getByNameAndVersion(connectedNode.type, connectedNode.typeVersion);
2386
+ if (!connectedNodeType) {
2387
+ continue;
2388
+ }
2389
+ if (connectedNodeType && !connectedNodeType.supplyData) {
2390
+ throw new workflow_1.NodeOperationError(connectedNode, "Node does not have a `supplyData` method defined", {
2391
+ itemIndex,
2392
+ });
2393
+ }
2394
+ try {
2395
+ await additionalData.hooks?.executeHookFunctions("nodeExecuteBefore", [
2396
+ connectedNode.name,
2397
+ {
2398
+ startTime: startTime,
2399
+ executionTime: Date.now() - startTime,
2400
+ },
2401
+ ]);
2402
+ const context = getSupplyDataFunctions(workflow, runExecutionData, runIndex, connectionInputData, inputData, connectedNode, additionalData, mode, nodeTypeData, closeFunctions);
2403
+ const supplyData = await connectedNodeType.supplyData?.call(context, itemIndex);
2404
+ if (supplyData) {
2405
+ nodes.push(supplyData);
2406
+ if (supplyData.closeFunction) {
2407
+ closeFunctions.push(supplyData.closeFunction);
2408
+ }
2409
+ const taskData = {
2410
+ startTime: startTime,
2411
+ executionTime: Date.now() - startTime,
2412
+ };
2413
+ if (!Object.hasOwn(runExecutionData.resultData.runData, connectedNode.name)) {
2414
+ runExecutionData.resultData.runData[connectedNode.name] = [];
2415
+ }
2416
+ runExecutionData.resultData.runData[connectedNode.name][runIndex] =
2417
+ taskData;
2418
+ await additionalData.hooks?.executeHookFunctions("nodeExecuteAfter", [
2419
+ connectedNode.name,
2420
+ taskData,
2421
+ runExecutionData,
2422
+ ]);
2423
+ }
2424
+ }
2425
+ catch (error) {
2426
+ const taskData = {
2427
+ startTime: startTime,
2428
+ executionTime: Date.now() - startTime,
2429
+ error: error,
2430
+ };
2431
+ runExecutionData.resultData.runData[connectedNode.name][runIndex] =
2432
+ taskData;
2433
+ await additionalData.hooks?.executeHookFunctions("nodeExecuteAfter", [
2434
+ connectedNode.name,
2435
+ taskData,
2436
+ runExecutionData,
2437
+ ]);
2438
+ throw new workflow_1.NodeOperationError(connectedNode, `Error in sub-node ${connectedNode.name}`, {
2439
+ itemIndex,
2440
+ description: error instanceof Error ? error.message : "Unknown error",
2441
+ });
2442
+ }
2443
+ }
2444
+ return inputConfiguration.maxConnections === 1
2445
+ ? (nodes || [])[0]?.response
2446
+ : nodes.map((node) => node.response);
2447
+ }
2448
+ function getNodeInputs(workflow, node, nodeTypeData) {
2449
+ if (Array.isArray(nodeTypeData?.inputs)) {
2450
+ return nodeTypeData.inputs;
2451
+ }
2452
+ // Calculate the outputs dynamically
2453
+ try {
2454
+ return (workflow.expression.getSimpleParameterValue(node, nodeTypeData.inputs, "internal", {}) || []);
2455
+ }
2456
+ catch (_e) {
2457
+ console.warn("Could not calculate inputs dynamically for node: ", node.name);
2458
+ return [];
2459
+ }
2460
+ }
2461
+ function getConnectedNodes(workflow, node, connectionType) {
2462
+ return workflow
2463
+ .getParentNodes(node.name, connectionType, 1)
2464
+ .map((nodeName) => workflow.getNode(nodeName))
2465
+ .filter((node) => !!node)
2466
+ .filter((node) => node.disabled !== true);
2467
+ }