@fsai-flow/core 0.0.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.
- package/README.md +11 -0
- package/dist/README.md +11 -0
- package/dist/package.json +44 -0
- package/dist/src/index.d.ts +15 -0
- package/dist/src/index.js +29 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lib/ActiveWebhooks.d.ts +59 -0
- package/dist/src/lib/ActiveWebhooks.js +184 -0
- package/dist/src/lib/ActiveWebhooks.js.map +1 -0
- package/dist/src/lib/ActiveWorkflows.d.ts +58 -0
- package/dist/src/lib/ActiveWorkflows.js +244 -0
- package/dist/src/lib/ActiveWorkflows.js.map +1 -0
- package/dist/src/lib/BinaryDataManager/FileSystem.d.ts +26 -0
- package/dist/src/lib/BinaryDataManager/FileSystem.js +179 -0
- package/dist/src/lib/BinaryDataManager/FileSystem.js.map +1 -0
- package/dist/src/lib/BinaryDataManager/index.d.ts +21 -0
- package/dist/src/lib/BinaryDataManager/index.js +146 -0
- package/dist/src/lib/BinaryDataManager/index.js.map +1 -0
- package/dist/src/lib/ChangeCase.d.ts +9 -0
- package/dist/src/lib/ChangeCase.js +43 -0
- package/dist/src/lib/ChangeCase.js.map +1 -0
- package/dist/src/lib/Constants.d.ts +14 -0
- package/dist/src/lib/Constants.js +19 -0
- package/dist/src/lib/Constants.js.map +1 -0
- package/dist/src/lib/Credentials.d.ts +27 -0
- package/dist/src/lib/Credentials.js +89 -0
- package/dist/src/lib/Credentials.js.map +1 -0
- package/dist/src/lib/FileSystem.d.ts +26 -0
- package/dist/src/lib/FileSystem.js +179 -0
- package/dist/src/lib/FileSystem.js.map +1 -0
- package/dist/src/lib/InputConnectionDataLegacy.d.ts +2 -0
- package/dist/src/lib/InputConnectionDataLegacy.js +79 -0
- package/dist/src/lib/InputConnectionDataLegacy.js.map +1 -0
- package/dist/src/lib/Interfaces.d.ts +148 -0
- package/dist/src/lib/Interfaces.js +3 -0
- package/dist/src/lib/Interfaces.js.map +1 -0
- package/dist/src/lib/LoadNodeParameterOptions.d.ts +39 -0
- package/dist/src/lib/LoadNodeParameterOptions.js +150 -0
- package/dist/src/lib/LoadNodeParameterOptions.js.map +1 -0
- package/dist/src/lib/NodeExecuteFunctions.d.ts +226 -0
- package/dist/src/lib/NodeExecuteFunctions.js +2483 -0
- package/dist/src/lib/NodeExecuteFunctions.js.map +1 -0
- package/dist/src/lib/NodesLoader/constants.d.ts +5 -0
- package/dist/src/lib/NodesLoader/constants.js +106 -0
- package/dist/src/lib/NodesLoader/constants.js.map +1 -0
- package/dist/src/lib/NodesLoader/custom-directory-loader.d.ts +9 -0
- package/dist/src/lib/NodesLoader/custom-directory-loader.js +36 -0
- package/dist/src/lib/NodesLoader/custom-directory-loader.js.map +1 -0
- package/dist/src/lib/NodesLoader/directory-loader.d.ts +66 -0
- package/dist/src/lib/NodesLoader/directory-loader.js +325 -0
- package/dist/src/lib/NodesLoader/directory-loader.js.map +1 -0
- package/dist/src/lib/NodesLoader/index.d.ts +5 -0
- package/dist/src/lib/NodesLoader/index.js +12 -0
- package/dist/src/lib/NodesLoader/index.js.map +1 -0
- package/dist/src/lib/NodesLoader/lazy-package-directory-loader.d.ts +7 -0
- package/dist/src/lib/NodesLoader/lazy-package-directory-loader.js +52 -0
- package/dist/src/lib/NodesLoader/lazy-package-directory-loader.js.map +1 -0
- package/dist/src/lib/NodesLoader/load-class-in-isolation.d.ts +1 -0
- package/dist/src/lib/NodesLoader/load-class-in-isolation.js +22 -0
- package/dist/src/lib/NodesLoader/load-class-in-isolation.js.map +1 -0
- package/dist/src/lib/NodesLoader/package-directory-loader.d.ts +17 -0
- package/dist/src/lib/NodesLoader/package-directory-loader.js +100 -0
- package/dist/src/lib/NodesLoader/package-directory-loader.js.map +1 -0
- package/dist/src/lib/NodesLoader/types.d.ts +14 -0
- package/dist/src/lib/NodesLoader/types.js +3 -0
- package/dist/src/lib/NodesLoader/types.js.map +1 -0
- package/dist/src/lib/UserSettings.d.ts +80 -0
- package/dist/src/lib/UserSettings.js +261 -0
- package/dist/src/lib/UserSettings.js.map +1 -0
- package/dist/src/lib/WorkflowExecute.d.ts +53 -0
- package/dist/src/lib/WorkflowExecute.js +835 -0
- package/dist/src/lib/WorkflowExecute.js.map +1 -0
- package/dist/src/lib/index.d.ts +21 -0
- package/dist/src/lib/index.js +146 -0
- package/dist/src/lib/index.js.map +1 -0
- package/dist/src/utils/crypto.d.ts +1 -0
- package/dist/src/utils/crypto.js +8 -0
- package/dist/src/utils/crypto.js.map +1 -0
- package/eslint.config.js +19 -0
- package/jest.config.ts +10 -0
- package/package.json +44 -0
- package/project.json +19 -0
- package/src/index.ts +27 -0
- package/src/lib/ActiveWebhooks.ts +245 -0
- package/src/lib/ActiveWorkflows.ts +304 -0
- package/src/lib/BinaryDataManager/FileSystem.ts +214 -0
- package/src/lib/BinaryDataManager/index.ts +187 -0
- package/src/lib/ChangeCase.ts +45 -0
- package/src/lib/Constants.ts +16 -0
- package/src/lib/Credentials.ts +108 -0
- package/src/lib/FileSystem.ts +214 -0
- package/src/lib/InputConnectionDataLegacy.ts +123 -0
- package/src/lib/Interfaces.ts +338 -0
- package/src/lib/LoadNodeParameterOptions.ts +235 -0
- package/src/lib/NodeExecuteFunctions.ts +3704 -0
- package/src/lib/NodesLoader/constants.ts +112 -0
- package/src/lib/NodesLoader/custom-directory-loader.ts +31 -0
- package/src/lib/NodesLoader/directory-loader.ts +458 -0
- package/src/lib/NodesLoader/index.ts +5 -0
- package/src/lib/NodesLoader/lazy-package-directory-loader.ts +55 -0
- package/src/lib/NodesLoader/load-class-in-isolation.ts +19 -0
- package/src/lib/NodesLoader/package-directory-loader.ts +107 -0
- package/src/lib/NodesLoader/types.ts +14 -0
- package/src/lib/UserSettings.ts +292 -0
- package/src/lib/WorkflowExecute.ts +1108 -0
- package/src/lib/index.ts +187 -0
- package/src/utils/crypto.ts +5 -0
- package/tests/Credentials.test.ts +88 -0
- package/tests/Helpers.ts +808 -0
- package/tests/WorkflowExecute.test.ts +1242 -0
- package/tsconfig.json +42 -0
- package/tsconfig.lib.json +10 -0
- package/tsconfig.spec.json +14 -0
|
@@ -0,0 +1,3704 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GenericValue,
|
|
3
|
+
IAdditionalCredentialOptions,
|
|
4
|
+
IAllExecuteFunctions,
|
|
5
|
+
IBinaryData,
|
|
6
|
+
IContextObject,
|
|
7
|
+
ICredentialDataDecryptedObject,
|
|
8
|
+
ICredentialsExpressionResolveValues,
|
|
9
|
+
IDataObject,
|
|
10
|
+
IExecuteFunctions,
|
|
11
|
+
IExecuteResponsePromiseData,
|
|
12
|
+
IExecuteSingleFunctions,
|
|
13
|
+
IExecuteWorkflowInfo,
|
|
14
|
+
IHttpRequestOptions,
|
|
15
|
+
IN8nHttpFullResponse,
|
|
16
|
+
IN8nHttpResponse,
|
|
17
|
+
INode,
|
|
18
|
+
INodeCredentialDescription,
|
|
19
|
+
INodeCredentialsDetails,
|
|
20
|
+
INodeExecutionData,
|
|
21
|
+
INodeParameters,
|
|
22
|
+
INodeType,
|
|
23
|
+
IOAuth2Options,
|
|
24
|
+
IPollFunctions,
|
|
25
|
+
IRunExecutionData,
|
|
26
|
+
ITaskDataConnections,
|
|
27
|
+
ITriggerFunctions,
|
|
28
|
+
IWebhookData,
|
|
29
|
+
IWebhookDescription,
|
|
30
|
+
IWebhookFunctions,
|
|
31
|
+
IWorkflowDataProxyAdditionalKeys,
|
|
32
|
+
IWorkflowDataProxyData,
|
|
33
|
+
IWorkflowExecuteAdditionalData,
|
|
34
|
+
IWorkflowMetadata,
|
|
35
|
+
NodeApiError,
|
|
36
|
+
NodeHelpers,
|
|
37
|
+
NodeOperationError,
|
|
38
|
+
NodeParameterValue,
|
|
39
|
+
Workflow,
|
|
40
|
+
WorkflowActivateMode,
|
|
41
|
+
WorkflowDataProxy,
|
|
42
|
+
WorkflowExecuteMode,
|
|
43
|
+
LoggerProxy as Logger,
|
|
44
|
+
JsonObject,
|
|
45
|
+
NodeParameterValueType,
|
|
46
|
+
IGetNodeParameterOptions,
|
|
47
|
+
NodeTypeAndVersion,
|
|
48
|
+
IExecuteData,
|
|
49
|
+
ISupplyDataFunctions,
|
|
50
|
+
NodeConnectionType,
|
|
51
|
+
ExecutionError,
|
|
52
|
+
ITaskMetadata,
|
|
53
|
+
INodeInputConfiguration,
|
|
54
|
+
INodeTypeDescription,
|
|
55
|
+
SupplyData,
|
|
56
|
+
CloseFunction,
|
|
57
|
+
AiEvent,
|
|
58
|
+
AINodeConnectionType,
|
|
59
|
+
ITaskData,
|
|
60
|
+
} from '@fsai-flow/workflow';
|
|
61
|
+
|
|
62
|
+
import { Agent } from 'https';
|
|
63
|
+
import { stringify } from 'qs';
|
|
64
|
+
const clientOAuth1 = require('oauth-1.0a');
|
|
65
|
+
import { Token } from 'oauth-1.0a';
|
|
66
|
+
|
|
67
|
+
import ClientOAuth2, { Data, RequestObject } from 'client-oauth2';
|
|
68
|
+
import { AuthorizationCode, ClientCredentials, AccessToken, ModuleOptions } from 'simple-oauth2';
|
|
69
|
+
|
|
70
|
+
import * as crypto from 'crypto';
|
|
71
|
+
import * as url from 'url';
|
|
72
|
+
import { get } from 'lodash';
|
|
73
|
+
import { Request, Response } from 'express';
|
|
74
|
+
const FormData = require('form-data');
|
|
75
|
+
import * as path from 'path';
|
|
76
|
+
import { OptionsWithUri, OptionsWithUrl } from 'request';
|
|
77
|
+
import * as requestPromise from 'request-promise-native';
|
|
78
|
+
import { createHmac } from 'crypto';
|
|
79
|
+
import { fromBuffer } from 'file-type';
|
|
80
|
+
import { lookup } from 'mime-types';
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
import axios, {
|
|
84
|
+
AxiosPromise,
|
|
85
|
+
AxiosProxyConfig,
|
|
86
|
+
AxiosRequestConfig,
|
|
87
|
+
AxiosResponse,
|
|
88
|
+
Method,
|
|
89
|
+
} from 'axios';
|
|
90
|
+
import { URL, URLSearchParams } from 'url';
|
|
91
|
+
import { BinaryDataManager } from './BinaryDataManager';
|
|
92
|
+
import {
|
|
93
|
+
ICredentialTestFunctions,
|
|
94
|
+
IHookFunctions,
|
|
95
|
+
ILoadOptionsFunctions,
|
|
96
|
+
IResponseError,
|
|
97
|
+
IWorkflowSettings,
|
|
98
|
+
PLACEHOLDER_EMPTY_EXECUTION_ID,
|
|
99
|
+
} from '../../src';
|
|
100
|
+
import { arrayBuffer } from 'stream/consumers';
|
|
101
|
+
import { promises as fs } from 'fs';
|
|
102
|
+
|
|
103
|
+
axios.defaults.timeout = 900000;
|
|
104
|
+
// Prevent axios from adding x-form-www-urlencoded headers by default
|
|
105
|
+
axios.defaults.headers.post = {};
|
|
106
|
+
axios.defaults.headers.put = {};
|
|
107
|
+
axios.defaults.headers.patch = {};
|
|
108
|
+
axios.defaults.paramsSerializer = (params) => {
|
|
109
|
+
if (params instanceof URLSearchParams) {
|
|
110
|
+
return params.toString();
|
|
111
|
+
}
|
|
112
|
+
return stringify(params, { arrayFormat: 'indices' });
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const pushFormDataValue = (form: FormData, key: string, value: any) => {
|
|
116
|
+
// eslint-disable-next-line no-prototype-builtins
|
|
117
|
+
if (value?.hasOwnProperty('value') && Object.prototype.hasOwnProperty.call(value, 'options')) {
|
|
118
|
+
// @ts-ignore
|
|
119
|
+
form.append(key, value.value, value.options);
|
|
120
|
+
} else {
|
|
121
|
+
form.append(key, value);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const createFormDataObject = (data: object) => {
|
|
126
|
+
const formData = new FormData();
|
|
127
|
+
const keys = Object.keys(data);
|
|
128
|
+
keys.forEach((key) => {
|
|
129
|
+
// @ts-ignore
|
|
130
|
+
const formField = data[key];
|
|
131
|
+
|
|
132
|
+
if (formField instanceof Array) {
|
|
133
|
+
formField.forEach((item) => {
|
|
134
|
+
pushFormDataValue(formData, key, item);
|
|
135
|
+
});
|
|
136
|
+
} else {
|
|
137
|
+
pushFormDataValue(formData, key, formField);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
return formData;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
function searchForHeader(headers: IDataObject, headerName: string) {
|
|
144
|
+
if (headers === undefined) {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const headerNames = Object.keys(headers);
|
|
149
|
+
headerName = headerName.toLowerCase();
|
|
150
|
+
return headerNames.find((thisHeader) => thisHeader.toLowerCase() === headerName);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function generateContentLengthHeader(formData: any, headers: IDataObject) {
|
|
154
|
+
if (!formData || !formData.getLength) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
const length = await new Promise((res, rej) => {
|
|
159
|
+
formData.getLength((error: Error | null, length: number) => {
|
|
160
|
+
if (error) {
|
|
161
|
+
rej(error);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
res(length);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
headers = Object.assign(headers, {
|
|
168
|
+
'content-length': length,
|
|
169
|
+
});
|
|
170
|
+
} catch (error) {
|
|
171
|
+
Logger.error('Unable to calculate form data length', { error });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function parseRequestObject(requestObject: IDataObject) {
|
|
176
|
+
// This function is a temporary implementation
|
|
177
|
+
// That translates all http requests done via
|
|
178
|
+
// the request library to axios directly
|
|
179
|
+
// We are not using n8n's interface as it would
|
|
180
|
+
// an unnecessary step, considering the `request`
|
|
181
|
+
// helper can be deprecated and removed.
|
|
182
|
+
const axiosConfig: AxiosRequestConfig = {};
|
|
183
|
+
|
|
184
|
+
if (typeof requestObject['headers'] === 'object' && requestObject['headers'] !== null) {
|
|
185
|
+
axiosConfig.headers = requestObject['headers'] as any;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Let's start parsing the hardest part, which is the request body.
|
|
189
|
+
// The process here is as following?
|
|
190
|
+
// - Check if we have a `content-type` header. If this was set,
|
|
191
|
+
// we will follow
|
|
192
|
+
// - Check if the `form` property was set. If yes, then it's x-www-form-urlencoded
|
|
193
|
+
// - Check if the `formData` property exists. If yes, then it's multipart/form-data
|
|
194
|
+
// - Lastly, we should have a regular `body` that is probably a JSON.
|
|
195
|
+
|
|
196
|
+
const contentTypeHeaderKeyName =
|
|
197
|
+
axiosConfig.headers &&
|
|
198
|
+
Object.keys(axiosConfig.headers).find(
|
|
199
|
+
(headerName) => headerName.toLowerCase() === 'content-type',
|
|
200
|
+
);
|
|
201
|
+
const contentType =
|
|
202
|
+
contentTypeHeaderKeyName &&
|
|
203
|
+
axiosConfig.headers &&
|
|
204
|
+
(axiosConfig.headers[contentTypeHeaderKeyName] as unknown as string | undefined);
|
|
205
|
+
|
|
206
|
+
if (contentType === 'application/x-www-form-urlencoded' && requestObject['formData'] === undefined) {
|
|
207
|
+
// there are nodes incorrectly created, informing the content type header
|
|
208
|
+
// and also using formData. Request lib takes precedence for the formData.
|
|
209
|
+
// We will do the same.
|
|
210
|
+
// Merge body and form properties.
|
|
211
|
+
if (typeof requestObject['body'] === 'string') {
|
|
212
|
+
axiosConfig.data = requestObject['body'];
|
|
213
|
+
} else {
|
|
214
|
+
const allData = Object.assign(requestObject['body'] || {}, requestObject['form'] || {}) as Record<
|
|
215
|
+
string,
|
|
216
|
+
string
|
|
217
|
+
>;
|
|
218
|
+
if (requestObject['useQuerystring'] === true) {
|
|
219
|
+
axiosConfig.data = stringify(allData, { arrayFormat: 'repeat' });
|
|
220
|
+
} else {
|
|
221
|
+
axiosConfig.data = stringify(allData);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
} else if (contentType && contentType.includes('multipart/form-data') !== false) {
|
|
225
|
+
if (requestObject['formData'] !== undefined && requestObject['formData'] instanceof FormData) {
|
|
226
|
+
axiosConfig.data = requestObject['formData'];
|
|
227
|
+
} else {
|
|
228
|
+
const allData = {
|
|
229
|
+
...(requestObject['body'] as object | undefined),
|
|
230
|
+
...(requestObject['formData'] as object | undefined),
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
axiosConfig.data = createFormDataObject(allData);
|
|
234
|
+
}
|
|
235
|
+
// replace the existing header with a new one that
|
|
236
|
+
// contains the boundary property.
|
|
237
|
+
// @ts-ignore
|
|
238
|
+
delete axiosConfig.headers[contentTypeHeaderKeyName];
|
|
239
|
+
const headers = axiosConfig.data.getHeaders();
|
|
240
|
+
axiosConfig.headers = Object.assign(axiosConfig.headers || {}, headers);
|
|
241
|
+
if (!axiosConfig.headers) {
|
|
242
|
+
axiosConfig.headers = {};
|
|
243
|
+
}
|
|
244
|
+
await generateContentLengthHeader(axiosConfig.data, axiosConfig.headers as IDataObject);
|
|
245
|
+
} else {
|
|
246
|
+
// When using the `form` property it means the content should be x-www-form-urlencoded.
|
|
247
|
+
if (requestObject['form'] !== undefined && requestObject['body'] === undefined) {
|
|
248
|
+
// If we have only form
|
|
249
|
+
axiosConfig.data =
|
|
250
|
+
typeof requestObject['form'] === 'string'
|
|
251
|
+
? stringify(requestObject['form'], { format: 'RFC3986' })
|
|
252
|
+
: stringify(requestObject['form']).toString();
|
|
253
|
+
|
|
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'] = 'application/x-www-form-urlencoded';
|
|
260
|
+
} else {
|
|
261
|
+
axiosConfig.headers = {
|
|
262
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
} else if (requestObject['formData'] !== undefined) {
|
|
266
|
+
// remove any "content-type" that might exist.
|
|
267
|
+
if (axiosConfig.headers !== undefined) {
|
|
268
|
+
const headers = Object.keys(axiosConfig.headers);
|
|
269
|
+
headers.forEach((header) =>
|
|
270
|
+
header.toLowerCase() === 'content-type' && axiosConfig.headers ? delete axiosConfig.headers[header] : null,
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (requestObject['formData'] instanceof FormData) {
|
|
275
|
+
axiosConfig.data = requestObject['formData'];
|
|
276
|
+
} else {
|
|
277
|
+
axiosConfig.data = createFormDataObject(requestObject['formData'] as object);
|
|
278
|
+
}
|
|
279
|
+
// Mix in headers as FormData creates the boundary.
|
|
280
|
+
const headers = axiosConfig.data.getHeaders();
|
|
281
|
+
axiosConfig.headers = Object.assign(axiosConfig.headers || {}, headers);
|
|
282
|
+
await generateContentLengthHeader(axiosConfig.data, axiosConfig.headers as IDataObject);
|
|
283
|
+
} else if (requestObject['body'] !== undefined) {
|
|
284
|
+
// If we have body and possibly form
|
|
285
|
+
if (requestObject['form'] !== undefined) {
|
|
286
|
+
// merge both objects when exist.
|
|
287
|
+
if (requestObject['body'] && requestObject['form']) {
|
|
288
|
+
requestObject['body'] = Object.assign(requestObject['body'], requestObject['form']);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
axiosConfig.data = requestObject['body'] as FormData | GenericValue | GenericValue[];
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (requestObject['uri'] !== undefined) {
|
|
296
|
+
axiosConfig.url = requestObject['uri']?.toString() as string;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (requestObject['url'] !== undefined) {
|
|
300
|
+
axiosConfig.url = requestObject['url']?.toString() as string;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (requestObject['baseURL'] !== undefined) {
|
|
304
|
+
axiosConfig.baseURL = requestObject['baseURL']?.toString() as string;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (requestObject['method'] !== undefined) {
|
|
308
|
+
axiosConfig.method = requestObject['method'] as Method;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (requestObject['qs'] !== undefined && Object.keys(requestObject['qs'] as object).length > 0) {
|
|
312
|
+
axiosConfig.params = requestObject['qs'] as IDataObject;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (
|
|
316
|
+
requestObject['useQuerystring'] === true ||
|
|
317
|
+
// @ts-ignore
|
|
318
|
+
requestObject.qsStringifyOptions?.arrayFormat === 'repeat'
|
|
319
|
+
) {
|
|
320
|
+
axiosConfig.paramsSerializer = (params) => {
|
|
321
|
+
return stringify(params, { arrayFormat: 'repeat' });
|
|
322
|
+
};
|
|
323
|
+
} else if (requestObject['useQuerystring'] === false) {
|
|
324
|
+
axiosConfig.paramsSerializer = (params) => {
|
|
325
|
+
return stringify(params, { arrayFormat: 'indices' });
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// @ts-ignore
|
|
330
|
+
if (requestObject.qsStringifyOptions?.arrayFormat === 'brackets') {
|
|
331
|
+
axiosConfig.paramsSerializer = (params) => {
|
|
332
|
+
return stringify(params, { arrayFormat: 'brackets' });
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (requestObject['auth'] !== undefined) {
|
|
337
|
+
// Check support for sendImmediately
|
|
338
|
+
if ((requestObject['auth'] as IDataObject)['bearer'] !== undefined) {
|
|
339
|
+
axiosConfig.headers = Object.assign(axiosConfig.headers || {}, {
|
|
340
|
+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
341
|
+
Authorization: `Bearer ${(requestObject['auth'] as IDataObject)['bearer']}`,
|
|
342
|
+
});
|
|
343
|
+
} else {
|
|
344
|
+
const authObj = requestObject['auth'] as IDataObject;
|
|
345
|
+
// Request accepts both user/username and pass/password
|
|
346
|
+
axiosConfig.auth = {
|
|
347
|
+
username: (authObj['user'] || authObj['username']) as string,
|
|
348
|
+
password: (authObj['password'] || authObj['pass']) as string,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Only set header if we have a body, otherwise it may fail
|
|
354
|
+
if (requestObject['json'] === true) {
|
|
355
|
+
// Add application/json headers - do not set charset as it breaks a lot of stuff
|
|
356
|
+
// only add if no other accept headers was sent.
|
|
357
|
+
const acceptHeaderExists =
|
|
358
|
+
axiosConfig.headers === undefined
|
|
359
|
+
? false
|
|
360
|
+
: Object.keys(axiosConfig.headers)
|
|
361
|
+
.map((headerKey) => headerKey.toLowerCase())
|
|
362
|
+
.includes('accept');
|
|
363
|
+
if (!acceptHeaderExists) {
|
|
364
|
+
axiosConfig.headers = Object.assign(axiosConfig.headers || {}, {
|
|
365
|
+
Accept: 'application/json',
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
if (requestObject['json'] === false || requestObject['json'] === undefined) {
|
|
370
|
+
// Prevent json parsing
|
|
371
|
+
axiosConfig.transformResponse = (res) => res;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Axios will follow redirects by default, so we simply tell it otherwise if needed.
|
|
375
|
+
if (
|
|
376
|
+
requestObject['followRedirect'] === false &&
|
|
377
|
+
((requestObject['method'] as string | undefined) || 'get').toLowerCase() === 'get'
|
|
378
|
+
) {
|
|
379
|
+
axiosConfig.maxRedirects = 0;
|
|
380
|
+
}
|
|
381
|
+
if (
|
|
382
|
+
requestObject['followAllRedirects'] === false &&
|
|
383
|
+
((requestObject['method'] as string | undefined) || 'get').toLowerCase() !== 'get'
|
|
384
|
+
) {
|
|
385
|
+
axiosConfig.maxRedirects = 0;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (requestObject['rejectUnauthorized'] === false) {
|
|
389
|
+
axiosConfig.httpsAgent = new Agent({
|
|
390
|
+
rejectUnauthorized: false,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (requestObject['timeout'] !== undefined) {
|
|
395
|
+
axiosConfig.timeout = requestObject['timeout'] as number;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (requestObject['proxy'] !== undefined) {
|
|
399
|
+
// try our best to parse the url provided.
|
|
400
|
+
if (typeof requestObject['proxy'] === 'string') {
|
|
401
|
+
try {
|
|
402
|
+
const url = new URL(requestObject['proxy']);
|
|
403
|
+
axiosConfig.proxy = {
|
|
404
|
+
host: url.hostname,
|
|
405
|
+
port: parseInt(url.port, 10),
|
|
406
|
+
protocol: url.protocol,
|
|
407
|
+
};
|
|
408
|
+
if (!url.port) {
|
|
409
|
+
// Sets port to a default if not informed
|
|
410
|
+
if (url.protocol === 'http') {
|
|
411
|
+
axiosConfig.proxy.port = 80;
|
|
412
|
+
} else if (url.protocol === 'https') {
|
|
413
|
+
axiosConfig.proxy.port = 443;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (url.username || url.password) {
|
|
417
|
+
axiosConfig.proxy.auth = {
|
|
418
|
+
username: url.username,
|
|
419
|
+
password: url.password,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
} catch (error) {
|
|
423
|
+
// Not a valid URL. We will try to simply parse stuff
|
|
424
|
+
// such as user:pass@host:port without protocol (we'll assume http)
|
|
425
|
+
if (requestObject['proxy'].includes('@')) {
|
|
426
|
+
const [userpass, hostport] = requestObject['proxy'].split('@');
|
|
427
|
+
const [username, password] = userpass.split(':');
|
|
428
|
+
const [hostname, port] = hostport.split(':');
|
|
429
|
+
axiosConfig.proxy = {
|
|
430
|
+
host: hostname,
|
|
431
|
+
port: parseInt(port, 10),
|
|
432
|
+
protocol: 'http',
|
|
433
|
+
auth: {
|
|
434
|
+
username,
|
|
435
|
+
password,
|
|
436
|
+
},
|
|
437
|
+
};
|
|
438
|
+
} else if (requestObject['proxy'].includes(':')) {
|
|
439
|
+
const [hostname, port] = requestObject['proxy'].split(':');
|
|
440
|
+
axiosConfig.proxy = {
|
|
441
|
+
host: hostname,
|
|
442
|
+
port: parseInt(port, 10),
|
|
443
|
+
protocol: 'http',
|
|
444
|
+
};
|
|
445
|
+
} else {
|
|
446
|
+
axiosConfig.proxy = {
|
|
447
|
+
host: requestObject['proxy'],
|
|
448
|
+
port: 80,
|
|
449
|
+
protocol: 'http',
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
} else {
|
|
454
|
+
axiosConfig.proxy = requestObject['proxy'] as AxiosProxyConfig;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (requestObject['encoding'] === null) {
|
|
459
|
+
// When downloading files, return an arrayBuffer.
|
|
460
|
+
axiosConfig.responseType = 'arraybuffer';
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// If we don't set an accept header
|
|
464
|
+
// Axios forces "application/json, text/plan, */*"
|
|
465
|
+
// Which causes some nodes like NextCloud to break
|
|
466
|
+
// as the service returns XML unless requested otherwise.
|
|
467
|
+
const allHeaders = axiosConfig.headers ? Object.keys(axiosConfig.headers) : [];
|
|
468
|
+
if (!allHeaders.some((headerKey) => headerKey.toLowerCase() === 'accept')) {
|
|
469
|
+
axiosConfig.headers = Object.assign(axiosConfig.headers || {}, { accept: '*/*' });
|
|
470
|
+
}
|
|
471
|
+
if (
|
|
472
|
+
requestObject['json'] !== false &&
|
|
473
|
+
axiosConfig.data !== undefined &&
|
|
474
|
+
axiosConfig.data !== '' &&
|
|
475
|
+
!(axiosConfig.data instanceof Buffer) &&
|
|
476
|
+
!allHeaders.some((headerKey) => headerKey.toLowerCase() === 'content-type')
|
|
477
|
+
) {
|
|
478
|
+
// Use default header for application/json
|
|
479
|
+
// If we don't specify this here, axios will add
|
|
480
|
+
// application/json; charset=utf-8
|
|
481
|
+
// and this breaks a lot of stuff
|
|
482
|
+
axiosConfig.headers = Object.assign(axiosConfig.headers || {}, {
|
|
483
|
+
'content-type': 'application/json',
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Missing properties:
|
|
489
|
+
* encoding (need testing)
|
|
490
|
+
* gzip (ignored - default already works)
|
|
491
|
+
* resolveWithFullResponse (implemented elsewhere)
|
|
492
|
+
* simple (???)
|
|
493
|
+
*/
|
|
494
|
+
|
|
495
|
+
return axiosConfig;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function digestAuthAxiosConfig(
|
|
499
|
+
axiosConfig: AxiosRequestConfig,
|
|
500
|
+
response: AxiosResponse,
|
|
501
|
+
auth: AxiosRequestConfig['auth'],
|
|
502
|
+
): AxiosRequestConfig {
|
|
503
|
+
const authDetails = response.headers['www-authenticate']
|
|
504
|
+
.split(',')
|
|
505
|
+
.map((v: string) => v.split('='));
|
|
506
|
+
if (authDetails) {
|
|
507
|
+
const nonceCount = `000000001`;
|
|
508
|
+
const cnonce = crypto.randomBytes(24).toString('hex');
|
|
509
|
+
const realm: string = authDetails
|
|
510
|
+
.find((el: any) => el[0].toLowerCase().indexOf('realm') > -1)[1]
|
|
511
|
+
.replace(/"/g, '');
|
|
512
|
+
const opaque: string = authDetails
|
|
513
|
+
.find((el: any) => el[0].toLowerCase().indexOf('opaque') > -1)[1]
|
|
514
|
+
.replace(/"/g, '');
|
|
515
|
+
const nonce: string = authDetails
|
|
516
|
+
.find((el: any) => el[0].toLowerCase().indexOf('nonce') > -1)[1]
|
|
517
|
+
.replace(/"/g, '');
|
|
518
|
+
const ha1 = crypto
|
|
519
|
+
.createHash('md5')
|
|
520
|
+
.update(`${auth?.username as string}:${realm}:${auth?.password as string}`)
|
|
521
|
+
.digest('hex');
|
|
522
|
+
const path = new url.URL(axiosConfig.url!).pathname;
|
|
523
|
+
const ha2 = crypto
|
|
524
|
+
.createHash('md5')
|
|
525
|
+
.update(`${axiosConfig.method ?? 'GET'}:${path}`)
|
|
526
|
+
.digest('hex');
|
|
527
|
+
const response = crypto
|
|
528
|
+
.createHash('md5')
|
|
529
|
+
.update(`${ha1}:${nonce}:${nonceCount}:${cnonce}:auth:${ha2}`)
|
|
530
|
+
.digest('hex');
|
|
531
|
+
const authorization =
|
|
532
|
+
`Digest username="${auth?.username as string}",realm="${realm}",` +
|
|
533
|
+
`nonce="${nonce}",uri="${path}",qop="auth",algorithm="MD5",` +
|
|
534
|
+
`response="${response}",nc="${nonceCount}",cnonce="${cnonce}",opaque="${opaque}"`;
|
|
535
|
+
if (axiosConfig.headers) {
|
|
536
|
+
axiosConfig.headers['authorization'] = authorization;
|
|
537
|
+
} else {
|
|
538
|
+
axiosConfig.headers = { authorization };
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
return axiosConfig;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
async function proxyRequestToAxios(
|
|
545
|
+
uriOrObject: string | IDataObject,
|
|
546
|
+
options?: IDataObject
|
|
547
|
+
): Promise<any> {
|
|
548
|
+
// tslint:disable-line:no-any
|
|
549
|
+
|
|
550
|
+
let axiosConfig: AxiosRequestConfig = {};
|
|
551
|
+
let axiosPromise: AxiosPromise;
|
|
552
|
+
type ConfigObject = {
|
|
553
|
+
auth?: { sendImmediately: boolean };
|
|
554
|
+
resolveWithFullResponse?: boolean;
|
|
555
|
+
simple?: boolean;
|
|
556
|
+
};
|
|
557
|
+
let configObject: ConfigObject;
|
|
558
|
+
if (uriOrObject !== undefined && typeof uriOrObject === 'string') {
|
|
559
|
+
axiosConfig.url = uriOrObject;
|
|
560
|
+
}
|
|
561
|
+
if (uriOrObject !== undefined && typeof uriOrObject === 'object') {
|
|
562
|
+
configObject = uriOrObject;
|
|
563
|
+
} else {
|
|
564
|
+
configObject = options || {};
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
axiosConfig = Object.assign(axiosConfig, await parseRequestObject(configObject));
|
|
568
|
+
|
|
569
|
+
Logger.debug(
|
|
570
|
+
'Proxying request to axios',
|
|
571
|
+
// {
|
|
572
|
+
// originalConfig: configObject,
|
|
573
|
+
// parsedConfig: axiosConfig,
|
|
574
|
+
// }
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
if (configObject.auth?.sendImmediately === false) {
|
|
578
|
+
// for digest-auth
|
|
579
|
+
const { auth } = axiosConfig;
|
|
580
|
+
delete axiosConfig.auth;
|
|
581
|
+
// eslint-disable-next-line no-async-promise-executor
|
|
582
|
+
axiosPromise = new Promise(async (resolve, reject) => {
|
|
583
|
+
try {
|
|
584
|
+
const result = await axios(axiosConfig);
|
|
585
|
+
resolve(result);
|
|
586
|
+
} catch (resp: any) {
|
|
587
|
+
if (
|
|
588
|
+
resp.response === undefined ||
|
|
589
|
+
resp.response.status !== 401 ||
|
|
590
|
+
!resp.response.headers['www-authenticate']?.includes('nonce')
|
|
591
|
+
) {
|
|
592
|
+
reject(resp);
|
|
593
|
+
}
|
|
594
|
+
axiosConfig = digestAuthAxiosConfig(axiosConfig, resp.response, auth);
|
|
595
|
+
resolve(axios(axiosConfig));
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
} else {
|
|
599
|
+
axiosPromise = axios(axiosConfig);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
return new Promise((resolve, reject) => {
|
|
603
|
+
axiosPromise
|
|
604
|
+
.then((response) => {
|
|
605
|
+
if (configObject.resolveWithFullResponse === true) {
|
|
606
|
+
let body = response.data;
|
|
607
|
+
if (response.data === '') {
|
|
608
|
+
if (axiosConfig.responseType === 'arraybuffer') {
|
|
609
|
+
body = Buffer.alloc(0);
|
|
610
|
+
} else {
|
|
611
|
+
body = undefined;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
resolve({
|
|
615
|
+
body,
|
|
616
|
+
headers: response.headers,
|
|
617
|
+
statusCode: response.status,
|
|
618
|
+
statusMessage: response.statusText,
|
|
619
|
+
request: response.request,
|
|
620
|
+
});
|
|
621
|
+
} else {
|
|
622
|
+
let body = response.data;
|
|
623
|
+
if (response.data === '') {
|
|
624
|
+
if (axiosConfig.responseType === 'arraybuffer') {
|
|
625
|
+
body = Buffer.alloc(0);
|
|
626
|
+
} else {
|
|
627
|
+
body = undefined;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
resolve(body);
|
|
631
|
+
}
|
|
632
|
+
})
|
|
633
|
+
.catch((error) => {
|
|
634
|
+
if (configObject.simple === false && error.response) {
|
|
635
|
+
if (configObject.resolveWithFullResponse) {
|
|
636
|
+
resolve({
|
|
637
|
+
body: error.response.data,
|
|
638
|
+
headers: error.response.headers,
|
|
639
|
+
statusCode: error.response.status,
|
|
640
|
+
statusMessage: error.response.statusText,
|
|
641
|
+
});
|
|
642
|
+
} else {
|
|
643
|
+
resolve(error.response.data);
|
|
644
|
+
}
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
Logger.debug('Request proxied to Axios failed', { error });
|
|
649
|
+
|
|
650
|
+
// Axios hydrates the original error with more data. We extract them.
|
|
651
|
+
// https://github.com/axios/axios/blob/master/lib/core/enhanceError.js
|
|
652
|
+
// Note: `code` is ignored as it's an expected part of the errorData.
|
|
653
|
+
const { request, response, isAxiosError, toJSON, config, ...errorData } = error;
|
|
654
|
+
if (response) {
|
|
655
|
+
error.message = `${response.status as number} - ${JSON.stringify(response.data)}`;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
error.cause = errorData;
|
|
659
|
+
error.error = error.response?.data || errorData;
|
|
660
|
+
error.statusCode = error.response?.status;
|
|
661
|
+
error.options = config || {};
|
|
662
|
+
|
|
663
|
+
// Remove not needed data and so also remove circular references
|
|
664
|
+
error.request = undefined;
|
|
665
|
+
error.config = undefined;
|
|
666
|
+
error.options.adapter = undefined;
|
|
667
|
+
error.options.httpsAgent = undefined;
|
|
668
|
+
error.options.paramsSerializer = undefined;
|
|
669
|
+
error.options.transformRequest = undefined;
|
|
670
|
+
error.options.transformResponse = undefined;
|
|
671
|
+
error.options.validateStatus = undefined;
|
|
672
|
+
|
|
673
|
+
reject(error);
|
|
674
|
+
});
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function convertN8nRequestToAxios(n8nRequest: IHttpRequestOptions): AxiosRequestConfig {
|
|
679
|
+
// Destructure properties with the same name first.
|
|
680
|
+
const { headers, method, timeout, auth, proxy, url } = n8nRequest;
|
|
681
|
+
|
|
682
|
+
const axiosRequest = {
|
|
683
|
+
headers: headers ?? {},
|
|
684
|
+
method,
|
|
685
|
+
timeout,
|
|
686
|
+
auth,
|
|
687
|
+
proxy,
|
|
688
|
+
url,
|
|
689
|
+
} as AxiosRequestConfig;
|
|
690
|
+
|
|
691
|
+
axiosRequest.params = n8nRequest.qs;
|
|
692
|
+
|
|
693
|
+
if (n8nRequest.baseURL !== undefined) {
|
|
694
|
+
axiosRequest.baseURL = n8nRequest.baseURL;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
if (n8nRequest.disableFollowRedirect === true) {
|
|
698
|
+
axiosRequest.maxRedirects = 0;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if (n8nRequest.encoding !== undefined) {
|
|
702
|
+
axiosRequest.responseType = n8nRequest.encoding;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
if (n8nRequest.skipSslCertificateValidation === true) {
|
|
706
|
+
axiosRequest.httpsAgent = new Agent({
|
|
707
|
+
rejectUnauthorized: false,
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
if (n8nRequest.arrayFormat !== undefined) {
|
|
712
|
+
axiosRequest.paramsSerializer = (params) => {
|
|
713
|
+
return stringify(params, { arrayFormat: n8nRequest.arrayFormat });
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
if (n8nRequest.body) {
|
|
718
|
+
axiosRequest.data = n8nRequest.body;
|
|
719
|
+
// Let's add some useful header standards here.
|
|
720
|
+
const existingContentTypeHeaderKey = axiosRequest.headers ? searchForHeader(axiosRequest.headers, 'content-type') : undefined;
|
|
721
|
+
if (existingContentTypeHeaderKey === undefined) {
|
|
722
|
+
// We are only setting content type headers if the user did
|
|
723
|
+
// not set it already manually. We're not overriding, even if it's wrong.
|
|
724
|
+
if (axiosRequest.data instanceof FormData) {
|
|
725
|
+
axiosRequest.headers = axiosRequest.headers || {};
|
|
726
|
+
axiosRequest.headers['Content-Type'] = 'multipart/form-data';
|
|
727
|
+
} else if (axiosRequest.data instanceof URLSearchParams) {
|
|
728
|
+
axiosRequest.headers = axiosRequest.headers || {};
|
|
729
|
+
axiosRequest.headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
if (n8nRequest.json) {
|
|
735
|
+
const key = axiosRequest.headers ? searchForHeader(axiosRequest.headers, 'accept') : undefined;
|
|
736
|
+
// If key exists, then the user has set both accept
|
|
737
|
+
// header and the json flag. Header should take precedence.
|
|
738
|
+
if (!key) {
|
|
739
|
+
axiosRequest.headers = axiosRequest.headers || {};
|
|
740
|
+
axiosRequest.headers.Accept = 'application/json';
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
const userAgentHeader = axiosRequest.headers ? searchForHeader(axiosRequest.headers as IDataObject, 'user-agent') : undefined;
|
|
745
|
+
// If key exists, then the user has set both accept
|
|
746
|
+
// header and the json flag. Header should take precedence.
|
|
747
|
+
if (!userAgentHeader) {
|
|
748
|
+
axiosRequest.headers = axiosRequest.headers || {};
|
|
749
|
+
axiosRequest.headers['User-Agent'] = 'n8n';
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
if (n8nRequest.ignoreHttpStatusErrors) {
|
|
753
|
+
axiosRequest.validateStatus = () => true;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
return axiosRequest;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
async function httpRequest(
|
|
760
|
+
requestOptions: IHttpRequestOptions,
|
|
761
|
+
): Promise<IN8nHttpFullResponse | IN8nHttpResponse> {
|
|
762
|
+
const axiosRequest = convertN8nRequestToAxios(requestOptions);
|
|
763
|
+
const result = await axios(axiosRequest);
|
|
764
|
+
if (requestOptions.returnFullResponse) {
|
|
765
|
+
return {
|
|
766
|
+
body: result.data,
|
|
767
|
+
headers: result.headers,
|
|
768
|
+
statusCode: result.status,
|
|
769
|
+
statusMessage: result.statusText,
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
return result.data;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Returns binary data buffer for given item index and property name.
|
|
777
|
+
*
|
|
778
|
+
* @export
|
|
779
|
+
* @param {ITaskDataConnections} inputData
|
|
780
|
+
* @param {number} itemIndex
|
|
781
|
+
* @param {string} propertyName
|
|
782
|
+
* @param {number} inputIndex
|
|
783
|
+
* @returns {Promise<Buffer>}
|
|
784
|
+
*/
|
|
785
|
+
export async function getBinaryDataBuffer(
|
|
786
|
+
inputData: ITaskDataConnections,
|
|
787
|
+
itemIndex: number,
|
|
788
|
+
propertyName: string,
|
|
789
|
+
inputIndex: number,
|
|
790
|
+
): Promise<Buffer> {
|
|
791
|
+
const binaryData = inputData['main']![inputIndex]![itemIndex]!.binary![propertyName]!;
|
|
792
|
+
return BinaryDataManager.getInstance().retrieveBinaryData(binaryData);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Takes a buffer and converts it into the format n8n uses. It encodes the binary data as
|
|
797
|
+
* base64 and adds metadata.
|
|
798
|
+
*
|
|
799
|
+
* @export
|
|
800
|
+
* @param {Buffer} binaryData
|
|
801
|
+
* @param {string} [filePath]
|
|
802
|
+
* @param {string} [mimeType]
|
|
803
|
+
* @returns {Promise<IBinaryData>}
|
|
804
|
+
*/
|
|
805
|
+
export async function prepareBinaryData(
|
|
806
|
+
binaryData: Buffer,
|
|
807
|
+
executionId: string,
|
|
808
|
+
filePath?: string,
|
|
809
|
+
mimeType?: string,
|
|
810
|
+
): Promise<IBinaryData> {
|
|
811
|
+
if (!mimeType) {
|
|
812
|
+
// If no mime type is given figure it out
|
|
813
|
+
|
|
814
|
+
if (filePath) {
|
|
815
|
+
// Use file path to guess mime type
|
|
816
|
+
const mimeTypeLookup = lookup(filePath);
|
|
817
|
+
if (mimeTypeLookup) {
|
|
818
|
+
mimeType = mimeTypeLookup;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
if (!mimeType) {
|
|
823
|
+
// Use buffer to guess mime type
|
|
824
|
+
const fileTypeData = await fromBuffer(binaryData);
|
|
825
|
+
if (fileTypeData) {
|
|
826
|
+
mimeType = fileTypeData.mime;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
if (!mimeType) {
|
|
831
|
+
// Fall back to text
|
|
832
|
+
mimeType = 'text/plain';
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
const returnData: IBinaryData = {
|
|
837
|
+
mimeType,
|
|
838
|
+
data: '',
|
|
839
|
+
};
|
|
840
|
+
|
|
841
|
+
if (filePath) {
|
|
842
|
+
if (filePath.includes('?')) {
|
|
843
|
+
// Remove maybe present query parameters
|
|
844
|
+
filePath = filePath.split('?').shift();
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
const filePathParts = path.parse(filePath as string);
|
|
848
|
+
|
|
849
|
+
if (filePathParts.dir !== '') {
|
|
850
|
+
returnData.directory = filePathParts.dir;
|
|
851
|
+
}
|
|
852
|
+
returnData.fileName = filePathParts.base;
|
|
853
|
+
|
|
854
|
+
// Remove the dot
|
|
855
|
+
const fileExtension = filePathParts.ext.slice(1);
|
|
856
|
+
if (fileExtension) {
|
|
857
|
+
returnData.fileExtension = fileExtension;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
return BinaryDataManager.getInstance().storeBinaryData(returnData, binaryData, executionId);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
|
|
865
|
+
export async function getCurrentOAuth2AccessToken(
|
|
866
|
+
this: IAllExecuteFunctions,
|
|
867
|
+
credentialsType: string,
|
|
868
|
+
node: INode,
|
|
869
|
+
additionalData: IWorkflowExecuteAdditionalData,
|
|
870
|
+
oAuth2Options?: IOAuth2Options,
|
|
871
|
+
): Promise<string> {
|
|
872
|
+
const credentials = await this.getCredentials(credentialsType) as ICredentialDataDecryptedObject;
|
|
873
|
+
if (!credentials) {
|
|
874
|
+
throw new NodeOperationError(node, `No credentials of type "${credentialsType}" were returned!`, { description: 'Credentials not found' });
|
|
875
|
+
}
|
|
876
|
+
const clientId = credentials['clientId'] as string;
|
|
877
|
+
const clientSecret = credentials['clientSecret'] as string;
|
|
878
|
+
const scope = credentials['scope'] as string;
|
|
879
|
+
const grantType = credentials['grantType'] as "authorizationCode" | "clientCredentials";
|
|
880
|
+
const authUrl = credentials['authUrl'] as string | undefined;
|
|
881
|
+
const accessTokenUrl = credentials['accessTokenUrl'] as string | undefined;
|
|
882
|
+
const httpAgent = credentials['httpAgent'] as string | undefined;
|
|
883
|
+
const oauthTokenData = (credentials['oauthTokenData'] ?? {}) as Record<string, unknown>;
|
|
884
|
+
let accessToken = (oauthTokenData?.['accessToken'] ?? "") as string;
|
|
885
|
+
let refreshToken = (oauthTokenData?.['refreshToken'] ?? "") as string;
|
|
886
|
+
let expiresAt = (oauthTokenData?.['expiresAt'] ?? "") as string;
|
|
887
|
+
let expiresIn = (oauthTokenData?.['expiresIn'] ?? 0) as number;
|
|
888
|
+
let tokenType = (oauthTokenData?.['tokenType'] ?? "") as string;
|
|
889
|
+
|
|
890
|
+
const isExpired = !accessToken || !expiresAt || new Date(expiresAt).getTime() <= Date.now();
|
|
891
|
+
|
|
892
|
+
if (isExpired) {
|
|
893
|
+
Logger.debug(`Node "${node.name}" is using credential "${credentialsType}", however the access token has expired. This will now be refreshed using the "${grantType}" flow.`);
|
|
894
|
+
|
|
895
|
+
if(!authUrl){
|
|
896
|
+
throw new NodeOperationError(node, `The "authUrl" property is required for the "${grantType}" flow. Please check your credentials configuration.`, { description: 'Missing authUrl' });
|
|
897
|
+
}
|
|
898
|
+
const config: ModuleOptions<'client_id'> = {
|
|
899
|
+
client: { id: clientId, secret: clientSecret },
|
|
900
|
+
auth: { tokenHost: authUrl, tokenPath: accessTokenUrl },
|
|
901
|
+
http: { agent: httpAgent },
|
|
902
|
+
options: { authorizationMethod: 'body' },
|
|
903
|
+
};
|
|
904
|
+
|
|
905
|
+
if (grantType === 'authorizationCode') {
|
|
906
|
+
const client = new AuthorizationCode(config);
|
|
907
|
+
|
|
908
|
+
let token = client.createToken({
|
|
909
|
+
access_token: get(oauthTokenData, oAuth2Options?.property as string) || accessToken,
|
|
910
|
+
refresh_token: refreshToken,
|
|
911
|
+
token_type: oAuth2Options?.tokenType || tokenType || 'Bearer',
|
|
912
|
+
expires_at: expiresAt,
|
|
913
|
+
expires_in: expiresIn,
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
const refreshOptions: any = oAuth2Options?.includeCredentialsOnRefreshOnBody ? { // Use of 'any' here is bad, though copying from previous example
|
|
917
|
+
body: {
|
|
918
|
+
client_id: clientId,
|
|
919
|
+
client_secret: clientSecret,
|
|
920
|
+
},
|
|
921
|
+
headers: {
|
|
922
|
+
Authorization: '', // override default basic auth
|
|
923
|
+
},
|
|
924
|
+
} : {};
|
|
925
|
+
|
|
926
|
+
token = await token.refresh(refreshOptions);
|
|
927
|
+
|
|
928
|
+
accessToken = token.token['access_token'] as string;
|
|
929
|
+
refreshToken = token.token['refresh_token'] as string || refreshToken
|
|
930
|
+
expiresAt = token.token['expires_at'] as string;
|
|
931
|
+
expiresIn = token.token['expires_in'] as number;
|
|
932
|
+
tokenType = token.token['token_type'] as string || 'Bearer';
|
|
933
|
+
} else if (grantType === 'clientCredentials') {
|
|
934
|
+
const client = new ClientCredentials(config);
|
|
935
|
+
const token = await client.getToken({scope});
|
|
936
|
+
|
|
937
|
+
accessToken = token.token['access_token'] as string;
|
|
938
|
+
refreshToken = token.token['refresh_token'] as string || refreshToken ;
|
|
939
|
+
expiresAt = token.token['expires_at'] as string;
|
|
940
|
+
expiresIn = token.token['expires_in'] as number;
|
|
941
|
+
tokenType = token.token['token_type'] as string || 'Bearer';
|
|
942
|
+
} else {
|
|
943
|
+
throw new Error(`Unsupported grant type "${grantType}" for Microsoft Graph API`);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
oauthTokenData["accessToken"] = accessToken;
|
|
947
|
+
oauthTokenData["refreshToken"] = refreshToken;
|
|
948
|
+
oauthTokenData["expiresAt"] = expiresAt;
|
|
949
|
+
oauthTokenData["expiresIn"] = expiresIn;
|
|
950
|
+
oauthTokenData["tokenType"] = tokenType;
|
|
951
|
+
|
|
952
|
+
if (!node.credentials || !node.credentials[credentialsType]) {
|
|
953
|
+
throw new Error(`Node "${node.name}" has no credentials of type "${credentialsType}"`);
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
await additionalData.credentialsHelper.updateCredentials(
|
|
957
|
+
node.credentials[credentialsType],
|
|
958
|
+
credentialsType,
|
|
959
|
+
credentials,
|
|
960
|
+
);
|
|
961
|
+
|
|
962
|
+
Logger.debug(`Access token for credential "${credentialsType}" has been refreshed.`);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
return accessToken;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
export async function requestOAuth2(
|
|
969
|
+
this: IAllExecuteFunctions,
|
|
970
|
+
credentialsType: string,
|
|
971
|
+
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions | IHttpRequestOptions,
|
|
972
|
+
node: INode,
|
|
973
|
+
additionalData: IWorkflowExecuteAdditionalData,
|
|
974
|
+
oAuth2Options?: IOAuth2Options,
|
|
975
|
+
isN8nRequest = false,
|
|
976
|
+
) {
|
|
977
|
+
const credentials = (await this.getCredentials(
|
|
978
|
+
credentialsType,
|
|
979
|
+
)) as ICredentialDataDecryptedObject;
|
|
980
|
+
|
|
981
|
+
if (!credentials) {
|
|
982
|
+
throw new Error('No credentials were returned!');
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
const oauthTokenData = credentials['oauthTokenData'] as Record<string, any>;
|
|
986
|
+
if (!oauthTokenData) {
|
|
987
|
+
throw new Error('OAuth credentials not connected!');
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
const config: ModuleOptions<'client_id'> = {
|
|
991
|
+
client: {
|
|
992
|
+
id: credentials['clientId'] as string,
|
|
993
|
+
secret: credentials['clientSecret'] as string,
|
|
994
|
+
},
|
|
995
|
+
auth: {
|
|
996
|
+
tokenHost: credentials['authUrl'] as string,
|
|
997
|
+
tokenPath: credentials['accessTokenUrl'] as string,
|
|
998
|
+
},
|
|
999
|
+
http: {
|
|
1000
|
+
agent: credentials['httpAgent'],
|
|
1001
|
+
},
|
|
1002
|
+
options: {
|
|
1003
|
+
authorizationMethod: 'body',
|
|
1004
|
+
},
|
|
1005
|
+
};
|
|
1006
|
+
|
|
1007
|
+
const client = new AuthorizationCode(config);
|
|
1008
|
+
|
|
1009
|
+
// Construct token from saved data
|
|
1010
|
+
const tokenObject: AccessToken = client.createToken({
|
|
1011
|
+
access_token: get(oauthTokenData, oAuth2Options?.property as string) || oauthTokenData['accessToken'],
|
|
1012
|
+
refresh_token: oauthTokenData['refreshToken'],
|
|
1013
|
+
token_type: oAuth2Options?.tokenType || oauthTokenData['tokenType'] || 'Bearer',
|
|
1014
|
+
expires_at: oauthTokenData['expiresAt'],
|
|
1015
|
+
expires_in: oauthTokenData['expiresIn'],
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
const currentToken = tokenObject;
|
|
1019
|
+
|
|
1020
|
+
const signedOptions = {
|
|
1021
|
+
...requestOptions,
|
|
1022
|
+
headers: {
|
|
1023
|
+
...(requestOptions.headers || {}),
|
|
1024
|
+
Authorization: `${currentToken.token["token_type"]} ${currentToken.token["access_token"]}`,
|
|
1025
|
+
},
|
|
1026
|
+
};
|
|
1027
|
+
|
|
1028
|
+
// Remove 'Bearer' if `keepBearer` is false
|
|
1029
|
+
if (oAuth2Options?.keepBearer === false && signedOptions.headers.Authorization) {
|
|
1030
|
+
signedOptions.headers.Authorization = signedOptions.headers.Authorization.split(' ')[1];
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
try {
|
|
1034
|
+
return await this.helpers['request']!(signedOptions);
|
|
1035
|
+
} catch (error: any) {
|
|
1036
|
+
const statusCodeReturned = oAuth2Options?.tokenExpiredStatusCode ?? 401;
|
|
1037
|
+
|
|
1038
|
+
if (error.statusCode === statusCodeReturned && currentToken.expired()) {
|
|
1039
|
+
// Refresh token
|
|
1040
|
+
Logger.debug(`OAuth2 token for "${credentialsType}" used by node "${node.name}" expired. Refreshing...`);
|
|
1041
|
+
Logger.info(`OAuth2 token for "${credentialsType}" used by node "${node.name}" expired. Refreshing...`);
|
|
1042
|
+
|
|
1043
|
+
const refreshOptions: any = {};
|
|
1044
|
+
|
|
1045
|
+
if (oAuth2Options?.includeCredentialsOnRefreshOnBody) {
|
|
1046
|
+
refreshOptions.body = {
|
|
1047
|
+
client_id: credentials['clientId'],
|
|
1048
|
+
client_secret: credentials['clientSecret'],
|
|
1049
|
+
};
|
|
1050
|
+
refreshOptions.headers = {
|
|
1051
|
+
Authorization: '', // override default basic auth
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
let refreshedToken: AccessToken;
|
|
1055
|
+
|
|
1056
|
+
try {
|
|
1057
|
+
refreshedToken = await currentToken.refresh(refreshOptions);
|
|
1058
|
+
} catch (error: any) {
|
|
1059
|
+
throw new Error("Failed to refresh token: " + error.message);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
credentials['oauthTokenData'] = {
|
|
1063
|
+
accessToken: refreshedToken.token["access_token"],
|
|
1064
|
+
refreshToken: refreshedToken.token["refresh_token"] || currentToken.token["refresh_token"],
|
|
1065
|
+
expiresAt: refreshedToken.token["expires_at"],
|
|
1066
|
+
expiresIn: refreshedToken.token["expires_in"],
|
|
1067
|
+
tokenType: refreshedToken.token["token_type"],
|
|
1068
|
+
} as any;
|
|
1069
|
+
|
|
1070
|
+
// Persist new token
|
|
1071
|
+
if (!node.credentials || !node.credentials[credentialsType]) {
|
|
1072
|
+
throw new Error(`Node "${node.name}" has no credentials of type "${credentialsType}"`);
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
await additionalData.credentialsHelper.updateCredentials(
|
|
1076
|
+
node.credentials[credentialsType],
|
|
1077
|
+
credentialsType,
|
|
1078
|
+
credentials,
|
|
1079
|
+
);
|
|
1080
|
+
|
|
1081
|
+
Logger.debug(`OAuth2 token for "${credentialsType}" used by node "${node.name}" has been renewed.`);
|
|
1082
|
+
Logger.info(`OAuth2 token for "${credentialsType}" used by node "${node.name}" has been renewed.`);
|
|
1083
|
+
|
|
1084
|
+
// Retry the original request with the new token
|
|
1085
|
+
const retryOptions = {
|
|
1086
|
+
...requestOptions,
|
|
1087
|
+
headers: {
|
|
1088
|
+
...(requestOptions.headers || {}),
|
|
1089
|
+
Authorization: `${refreshedToken.token["token_type"]} ${refreshedToken.token["access_token"]}`,
|
|
1090
|
+
},
|
|
1091
|
+
};
|
|
1092
|
+
|
|
1093
|
+
if (oAuth2Options?.keepBearer === false && retryOptions.headers.Authorization) {
|
|
1094
|
+
retryOptions.headers.Authorization = retryOptions.headers.Authorization.split(' ')[1];
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
return isN8nRequest
|
|
1098
|
+
? this.helpers.httpRequest(retryOptions as IHttpRequestOptions)
|
|
1099
|
+
: this.helpers['request']!(retryOptions);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
throw error;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
|
|
1107
|
+
/* Makes a request using OAuth1 data for authentication
|
|
1108
|
+
*
|
|
1109
|
+
* @export
|
|
1110
|
+
* @param {IAllExecuteFunctions} this
|
|
1111
|
+
* @param {string} credentialsType
|
|
1112
|
+
* @param {(OptionsWithUrl | requestPromise.RequestPromiseOptions)} requestOptionså
|
|
1113
|
+
* @returns
|
|
1114
|
+
*/
|
|
1115
|
+
export async function requestOAuth1(
|
|
1116
|
+
this: IAllExecuteFunctions,
|
|
1117
|
+
credentialsType: string,
|
|
1118
|
+
requestOptions:
|
|
1119
|
+
| OptionsWithUrl
|
|
1120
|
+
| OptionsWithUri
|
|
1121
|
+
| requestPromise.RequestPromiseOptions
|
|
1122
|
+
| IHttpRequestOptions,
|
|
1123
|
+
isN8nRequest = false,
|
|
1124
|
+
) {
|
|
1125
|
+
const credentials = (await this.getCredentials(
|
|
1126
|
+
credentialsType,
|
|
1127
|
+
)) as ICredentialDataDecryptedObject;
|
|
1128
|
+
|
|
1129
|
+
if (credentials === undefined) {
|
|
1130
|
+
throw new Error('No credentials were returned!');
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
if (credentials['oauthTokenData'] === undefined) {
|
|
1134
|
+
throw new Error('OAuth credentials not connected!');
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
const oauth = new clientOAuth1({
|
|
1138
|
+
consumer: {
|
|
1139
|
+
key: credentials['consumerKey'] as string,
|
|
1140
|
+
secret: credentials['consumerSecret'] as string,
|
|
1141
|
+
},
|
|
1142
|
+
signature_method: credentials['signatureMethod'] as string,
|
|
1143
|
+
hash_function(base: any, key: any) {
|
|
1144
|
+
const algorithm = credentials['signatureMethod'] === 'HMAC-SHA1' ? 'sha1' : 'sha256';
|
|
1145
|
+
return createHmac(algorithm, key).update(base).digest('base64');
|
|
1146
|
+
},
|
|
1147
|
+
});
|
|
1148
|
+
|
|
1149
|
+
const oauthTokenData = credentials['oauthTokenData'] as IDataObject;
|
|
1150
|
+
|
|
1151
|
+
const token: Token = {
|
|
1152
|
+
key: oauthTokenData['oauth_token'] as string,
|
|
1153
|
+
secret: oauthTokenData['oauth_token_secret'] as string,
|
|
1154
|
+
};
|
|
1155
|
+
|
|
1156
|
+
// @ts-ignore
|
|
1157
|
+
requestOptions.data = { ...requestOptions.qs, ...requestOptions.form };
|
|
1158
|
+
|
|
1159
|
+
// Fixes issue that OAuth1 library only works with "url" property and not with "uri"
|
|
1160
|
+
// @ts-ignore
|
|
1161
|
+
if (requestOptions.uri && !requestOptions.url) {
|
|
1162
|
+
// @ts-ignore
|
|
1163
|
+
requestOptions.url = requestOptions.uri;
|
|
1164
|
+
// @ts-ignore
|
|
1165
|
+
delete requestOptions.uri;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// @ts-ignore
|
|
1169
|
+
requestOptions.headers = oauth.toHeader(oauth.authorize(requestOptions, token));
|
|
1170
|
+
|
|
1171
|
+
if (isN8nRequest) {
|
|
1172
|
+
return this.helpers.httpRequest(requestOptions as IHttpRequestOptions);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
return this.helpers['request']!(requestOptions).catch(async (error: IResponseError) => {
|
|
1176
|
+
// Unknown error so simply throw it
|
|
1177
|
+
throw error;
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
export async function httpRequestWithAuthentication(
|
|
1182
|
+
this: IAllExecuteFunctions,
|
|
1183
|
+
credentialsType: string,
|
|
1184
|
+
requestOptions: IHttpRequestOptions,
|
|
1185
|
+
workflow: Workflow,
|
|
1186
|
+
node: INode,
|
|
1187
|
+
additionalData: IWorkflowExecuteAdditionalData,
|
|
1188
|
+
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
|
1189
|
+
) {
|
|
1190
|
+
try {
|
|
1191
|
+
const parentTypes = additionalData.credentialsHelper.getParentTypes(credentialsType);
|
|
1192
|
+
|
|
1193
|
+
if (parentTypes.includes('oAuth1Api')) {
|
|
1194
|
+
return await requestOAuth1.call(this, credentialsType, requestOptions, true);
|
|
1195
|
+
}
|
|
1196
|
+
if (parentTypes.includes('oAuth2Api')) {
|
|
1197
|
+
return await requestOAuth2.call(
|
|
1198
|
+
this,
|
|
1199
|
+
credentialsType,
|
|
1200
|
+
requestOptions,
|
|
1201
|
+
node,
|
|
1202
|
+
additionalData,
|
|
1203
|
+
additionalCredentialOptions?.oauth2,
|
|
1204
|
+
true,
|
|
1205
|
+
);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
let credentialsDecrypted: ICredentialDataDecryptedObject | undefined;
|
|
1209
|
+
if (additionalCredentialOptions?.credentialsDecrypted) {
|
|
1210
|
+
credentialsDecrypted = additionalCredentialOptions.credentialsDecrypted.data;
|
|
1211
|
+
} else {
|
|
1212
|
+
credentialsDecrypted = await this.getCredentials(credentialsType);
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
if (credentialsDecrypted === undefined) {
|
|
1216
|
+
throw new NodeOperationError(
|
|
1217
|
+
node,
|
|
1218
|
+
`Node "${node.name}" does not have any credentials of type "${credentialsType}" set!`,
|
|
1219
|
+
);
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
requestOptions = await additionalData.credentialsHelper.authenticate(
|
|
1223
|
+
credentialsDecrypted,
|
|
1224
|
+
credentialsType,
|
|
1225
|
+
requestOptions,
|
|
1226
|
+
workflow,
|
|
1227
|
+
node,
|
|
1228
|
+
);
|
|
1229
|
+
|
|
1230
|
+
return await httpRequest(requestOptions);
|
|
1231
|
+
} catch (error) {
|
|
1232
|
+
throw new NodeApiError(this.getNode(), error as JsonObject);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
/**
|
|
1237
|
+
* Takes generic input data and brings it into the json format n8n uses.
|
|
1238
|
+
*
|
|
1239
|
+
* @export
|
|
1240
|
+
* @param {(IDataObject | IDataObject[])} jsonData
|
|
1241
|
+
* @returns {INodeExecutionData[]}
|
|
1242
|
+
*/
|
|
1243
|
+
export function returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[] {
|
|
1244
|
+
const returnData: INodeExecutionData[] = [];
|
|
1245
|
+
|
|
1246
|
+
if (!Array.isArray(jsonData)) {
|
|
1247
|
+
jsonData = [jsonData];
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
jsonData.forEach((data) => {
|
|
1251
|
+
returnData.push({ json: data });
|
|
1252
|
+
});
|
|
1253
|
+
|
|
1254
|
+
return returnData;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
/**
|
|
1258
|
+
* Automatically put the objects under a 'json' key and don't error,
|
|
1259
|
+
* if some objects contain json/binary keys and others don't, throws error 'Inconsistent item format'
|
|
1260
|
+
*
|
|
1261
|
+
* @export
|
|
1262
|
+
* @param {INodeExecutionData | INodeExecutionData[]} executionData
|
|
1263
|
+
* @returns {INodeExecutionData[]}
|
|
1264
|
+
*/
|
|
1265
|
+
export function normalizeItems(
|
|
1266
|
+
executionData: INodeExecutionData | INodeExecutionData[],
|
|
1267
|
+
): INodeExecutionData[] {
|
|
1268
|
+
if (typeof executionData === 'object' && !Array.isArray(executionData))
|
|
1269
|
+
executionData = [{ json: executionData as IDataObject }];
|
|
1270
|
+
if (executionData.every((item) => typeof item === 'object' && 'json' in item))
|
|
1271
|
+
return executionData;
|
|
1272
|
+
|
|
1273
|
+
if (executionData.some((item) => typeof item === 'object' && 'json' in item)) {
|
|
1274
|
+
throw new Error('Inconsistent item format');
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
if (executionData.every((item) => typeof item === 'object' && 'binary' in item)) {
|
|
1278
|
+
const normalizedItems: INodeExecutionData[] = [];
|
|
1279
|
+
executionData.forEach((item) => {
|
|
1280
|
+
const json = Object.keys(item).reduce((acc, key) => {
|
|
1281
|
+
if (key === 'binary') return acc;
|
|
1282
|
+
return { ...acc, [key]: item[key] };
|
|
1283
|
+
}, {});
|
|
1284
|
+
|
|
1285
|
+
normalizedItems.push({
|
|
1286
|
+
json,
|
|
1287
|
+
binary: item.binary,
|
|
1288
|
+
});
|
|
1289
|
+
});
|
|
1290
|
+
return normalizedItems;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
if (executionData.some((item) => typeof item === 'object' && 'binary' in item)) {
|
|
1294
|
+
throw new Error('Inconsistent item format');
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
return executionData.map((item) => {
|
|
1298
|
+
return { json: item };
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
// TODO: Move up later
|
|
1303
|
+
export async function requestWithAuthentication(
|
|
1304
|
+
this: IAllExecuteFunctions,
|
|
1305
|
+
credentialsType: string,
|
|
1306
|
+
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
|
1307
|
+
workflow: Workflow,
|
|
1308
|
+
node: INode,
|
|
1309
|
+
additionalData: IWorkflowExecuteAdditionalData,
|
|
1310
|
+
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
|
1311
|
+
) {
|
|
1312
|
+
try {
|
|
1313
|
+
const parentTypes = additionalData.credentialsHelper.getParentTypes(credentialsType);
|
|
1314
|
+
|
|
1315
|
+
if (parentTypes.includes('oAuth1Api')) {
|
|
1316
|
+
return await requestOAuth1.call(this, credentialsType, requestOptions, false);
|
|
1317
|
+
}
|
|
1318
|
+
if (parentTypes.includes('oAuth2Api')) {
|
|
1319
|
+
return await requestOAuth2.call(
|
|
1320
|
+
this,
|
|
1321
|
+
credentialsType,
|
|
1322
|
+
requestOptions,
|
|
1323
|
+
node,
|
|
1324
|
+
additionalData,
|
|
1325
|
+
additionalCredentialOptions?.oauth2,
|
|
1326
|
+
false,
|
|
1327
|
+
);
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
let credentialsDecrypted: ICredentialDataDecryptedObject | undefined;
|
|
1331
|
+
if (additionalCredentialOptions?.credentialsDecrypted) {
|
|
1332
|
+
credentialsDecrypted = additionalCredentialOptions.credentialsDecrypted.data;
|
|
1333
|
+
} else {
|
|
1334
|
+
credentialsDecrypted = await this.getCredentials(credentialsType);
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
if (credentialsDecrypted === undefined) {
|
|
1338
|
+
throw new NodeOperationError(
|
|
1339
|
+
node,
|
|
1340
|
+
`Node "${node.name}" does not have any credentials of type "${credentialsType}" set!`,
|
|
1341
|
+
);
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
requestOptions = await additionalData.credentialsHelper.authenticate(
|
|
1345
|
+
credentialsDecrypted,
|
|
1346
|
+
credentialsType,
|
|
1347
|
+
requestOptions as IHttpRequestOptions,
|
|
1348
|
+
workflow,
|
|
1349
|
+
node,
|
|
1350
|
+
);
|
|
1351
|
+
|
|
1352
|
+
return await proxyRequestToAxios(requestOptions as IDataObject);
|
|
1353
|
+
} catch (error: any) {
|
|
1354
|
+
throw new NodeApiError(this.getNode(), error);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
/**
|
|
1359
|
+
* Returns the additional keys for Expressions and Function-Nodes
|
|
1360
|
+
*
|
|
1361
|
+
* @export
|
|
1362
|
+
* @param {IWorkflowExecuteAdditionalData} additionalData
|
|
1363
|
+
* @returns {(IWorkflowDataProxyAdditionalKeys)}
|
|
1364
|
+
*/
|
|
1365
|
+
export function getAdditionalKeys(
|
|
1366
|
+
additionalData: IWorkflowExecuteAdditionalData,
|
|
1367
|
+
): IWorkflowDataProxyAdditionalKeys {
|
|
1368
|
+
const executionId = additionalData.executionId || PLACEHOLDER_EMPTY_EXECUTION_ID;
|
|
1369
|
+
return {
|
|
1370
|
+
$executionId: executionId,
|
|
1371
|
+
$resumeWebhookUrl: `${additionalData.webhookWaitingBaseUrl}/${executionId}`,
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
/**
|
|
1376
|
+
* Returns the requested decrypted credentials if the node has access to them.
|
|
1377
|
+
*
|
|
1378
|
+
* @export
|
|
1379
|
+
* @param {Workflow} workflow Workflow which requests the data
|
|
1380
|
+
* @param {INode} node Node which request the data
|
|
1381
|
+
* @param {string} type The credential type to return
|
|
1382
|
+
* @param {IWorkflowExecuteAdditionalData} additionalData
|
|
1383
|
+
* @returns {(ICredentialDataDecryptedObject | undefined)}
|
|
1384
|
+
*/
|
|
1385
|
+
export async function getCredentials(
|
|
1386
|
+
workflow: Workflow,
|
|
1387
|
+
node: INode,
|
|
1388
|
+
type: string,
|
|
1389
|
+
additionalData: IWorkflowExecuteAdditionalData,
|
|
1390
|
+
mode: WorkflowExecuteMode,
|
|
1391
|
+
runExecutionData?: IRunExecutionData | null,
|
|
1392
|
+
runIndex?: number,
|
|
1393
|
+
connectionInputData?: INodeExecutionData[],
|
|
1394
|
+
itemIndex?: number,
|
|
1395
|
+
): Promise<ICredentialDataDecryptedObject | undefined> {
|
|
1396
|
+
// Get the NodeType as it has the information if the credentials are required
|
|
1397
|
+
const nodeType = workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
|
|
1398
|
+
if (nodeType === undefined) {
|
|
1399
|
+
throw new NodeOperationError(
|
|
1400
|
+
node,
|
|
1401
|
+
`Node type "${node.type}" is not known so can not get credentials!`,
|
|
1402
|
+
);
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
// Hardcode for now for security reasons that only a single node can access
|
|
1406
|
+
// all credentials
|
|
1407
|
+
const fullAccess = ['n8n-nodes-base.httpRequest'].includes(node.type);
|
|
1408
|
+
|
|
1409
|
+
let nodeCredentialDescription: INodeCredentialDescription | undefined;
|
|
1410
|
+
if (!fullAccess) {
|
|
1411
|
+
if (nodeType.description.credentials === undefined) {
|
|
1412
|
+
throw new NodeOperationError(
|
|
1413
|
+
node,
|
|
1414
|
+
`Node type "${node.type}" does not have any credentials defined!`,
|
|
1415
|
+
);
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
nodeCredentialDescription = nodeType.description.credentials.find(
|
|
1419
|
+
(credentialTypeDescription) => credentialTypeDescription.name === type,
|
|
1420
|
+
);
|
|
1421
|
+
if (nodeCredentialDescription === undefined) {
|
|
1422
|
+
throw new NodeOperationError(
|
|
1423
|
+
node,
|
|
1424
|
+
`Node type "${node.type}" does not have any credentials of type "${type}" defined!`,
|
|
1425
|
+
);
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
if (
|
|
1429
|
+
!NodeHelpers.displayParameter(
|
|
1430
|
+
additionalData.currentNodeParameters || node.parameters,
|
|
1431
|
+
nodeCredentialDescription,
|
|
1432
|
+
node.parameters,
|
|
1433
|
+
)
|
|
1434
|
+
) {
|
|
1435
|
+
// Credentials should not be displayed so return undefined even if they would be defined
|
|
1436
|
+
return undefined;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
// Check if node has any credentials defined
|
|
1441
|
+
if (!fullAccess && (!node.credentials || !node.credentials[type])) {
|
|
1442
|
+
// If none are defined check if the credentials are required or not
|
|
1443
|
+
|
|
1444
|
+
if (nodeCredentialDescription?.required === true) {
|
|
1445
|
+
// Credentials are required so error
|
|
1446
|
+
if (!node.credentials) {
|
|
1447
|
+
throw new NodeOperationError(node, 'Node does not have any credentials set!');
|
|
1448
|
+
}
|
|
1449
|
+
if (!node.credentials[type]) {
|
|
1450
|
+
throw new NodeOperationError(node, `Node does not have any credentials set for "${type}"!`);
|
|
1451
|
+
}
|
|
1452
|
+
} else {
|
|
1453
|
+
// Credentials are not required so resolve with undefined
|
|
1454
|
+
return undefined;
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
if (fullAccess && (!node.credentials || !node.credentials[type])) {
|
|
1459
|
+
// Make sure that fullAccess nodes still behave like before that if they
|
|
1460
|
+
// request access to credentials that are currently not set it returns undefined
|
|
1461
|
+
return undefined;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
let expressionResolveValues: ICredentialsExpressionResolveValues | undefined;
|
|
1465
|
+
if (connectionInputData && runExecutionData && runIndex !== undefined) {
|
|
1466
|
+
expressionResolveValues = {
|
|
1467
|
+
connectionInputData,
|
|
1468
|
+
itemIndex: itemIndex || 0,
|
|
1469
|
+
node,
|
|
1470
|
+
runExecutionData,
|
|
1471
|
+
runIndex,
|
|
1472
|
+
workflow,
|
|
1473
|
+
} as ICredentialsExpressionResolveValues;
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
const nodeCredentials = node.credentials
|
|
1477
|
+
? node.credentials[type]
|
|
1478
|
+
: ({} as INodeCredentialsDetails);
|
|
1479
|
+
|
|
1480
|
+
// TODO: solve using credentials via expression
|
|
1481
|
+
// if (name.charAt(0) === '=') {
|
|
1482
|
+
// // If the credential name is an expression resolve it
|
|
1483
|
+
// const additionalKeys = getAdditionalKeys(additionalData);
|
|
1484
|
+
// name = workflow.expression.getParameterValue(
|
|
1485
|
+
// name,
|
|
1486
|
+
// runExecutionData || null,
|
|
1487
|
+
// runIndex || 0,
|
|
1488
|
+
// itemIndex || 0,
|
|
1489
|
+
// node.name,
|
|
1490
|
+
// connectionInputData || [],
|
|
1491
|
+
// mode,
|
|
1492
|
+
// additionalKeys,
|
|
1493
|
+
// ) as string;
|
|
1494
|
+
// }
|
|
1495
|
+
|
|
1496
|
+
const decryptedDataObject = await additionalData.credentialsHelper.getDecrypted(
|
|
1497
|
+
nodeCredentials,
|
|
1498
|
+
type,
|
|
1499
|
+
mode,
|
|
1500
|
+
false,
|
|
1501
|
+
expressionResolveValues,
|
|
1502
|
+
);
|
|
1503
|
+
|
|
1504
|
+
return decryptedDataObject;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
/**
|
|
1508
|
+
* Returns a copy of the node
|
|
1509
|
+
*
|
|
1510
|
+
* @export
|
|
1511
|
+
* @param {INode} node
|
|
1512
|
+
* @returns {INode}
|
|
1513
|
+
*/
|
|
1514
|
+
export function getNode(node: INode): INode {
|
|
1515
|
+
return JSON.parse(JSON.stringify(node));
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
/**
|
|
1519
|
+
* Returns the requested resolved (all expressions replaced) node parameters.
|
|
1520
|
+
*
|
|
1521
|
+
* @export
|
|
1522
|
+
* @param {Workflow} workflow
|
|
1523
|
+
* @param {(IRunExecutionData | null)} runExecutionData
|
|
1524
|
+
* @param {number} runIndex
|
|
1525
|
+
* @param {INodeExecutionData[]} connectionInputData
|
|
1526
|
+
* @param {INode} node
|
|
1527
|
+
* @param {string} parameterName
|
|
1528
|
+
* @param {number} itemIndex
|
|
1529
|
+
* @param {*} [fallbackValue]
|
|
1530
|
+
* @returns {(NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object)}
|
|
1531
|
+
*/
|
|
1532
|
+
|
|
1533
|
+
export function getNodeParameter(
|
|
1534
|
+
workflow: Workflow,
|
|
1535
|
+
runExecutionData: IRunExecutionData | null,
|
|
1536
|
+
runIndex: number,
|
|
1537
|
+
connectionInputData: INodeExecutionData[],
|
|
1538
|
+
node: INode,
|
|
1539
|
+
parameterName: string,
|
|
1540
|
+
itemIndex: number,
|
|
1541
|
+
mode: WorkflowExecuteMode,
|
|
1542
|
+
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
|
1543
|
+
fallbackValue?: any,
|
|
1544
|
+
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object {
|
|
1545
|
+
const nodeType = workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
|
|
1546
|
+
if (nodeType === undefined) {
|
|
1547
|
+
throw new Error(`Node type "${node.type}" is not known so can not return paramter value!`);
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
const value = get(node.parameters, parameterName, fallbackValue);
|
|
1551
|
+
|
|
1552
|
+
if (value === undefined) {
|
|
1553
|
+
throw new Error(`Could not get parameter "${parameterName}"!`);
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
let returnData;
|
|
1557
|
+
try {
|
|
1558
|
+
returnData = workflow.expression.getParameterValue(
|
|
1559
|
+
value,
|
|
1560
|
+
runExecutionData,
|
|
1561
|
+
runIndex,
|
|
1562
|
+
itemIndex,
|
|
1563
|
+
node.name,
|
|
1564
|
+
connectionInputData,
|
|
1565
|
+
mode,
|
|
1566
|
+
additionalKeys,
|
|
1567
|
+
);
|
|
1568
|
+
} catch (e: any) {
|
|
1569
|
+
e.message += ` [Error in parameter: "${parameterName}"]`;
|
|
1570
|
+
throw e;
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
return returnData;
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
/**
|
|
1577
|
+
* Returns if execution should be continued even if there was an error.
|
|
1578
|
+
*
|
|
1579
|
+
* @export
|
|
1580
|
+
* @param {INode} node
|
|
1581
|
+
* @returns {boolean}
|
|
1582
|
+
*/
|
|
1583
|
+
export function continueOnFail(node: INode): boolean {
|
|
1584
|
+
return get(node, 'continueOnFail', false);
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
/**
|
|
1588
|
+
* Returns the webhook URL of the webhook with the given name
|
|
1589
|
+
*
|
|
1590
|
+
* @export
|
|
1591
|
+
* @param {string} name
|
|
1592
|
+
* @param {Workflow} workflow
|
|
1593
|
+
* @param {INode} node
|
|
1594
|
+
* @param {IWorkflowExecuteAdditionalData} additionalData
|
|
1595
|
+
* @param {boolean} [isTest]
|
|
1596
|
+
* @returns {(string | undefined)}
|
|
1597
|
+
*/
|
|
1598
|
+
export function getNodeWebhookUrl(
|
|
1599
|
+
name: string,
|
|
1600
|
+
workflow: Workflow,
|
|
1601
|
+
node: INode,
|
|
1602
|
+
additionalData: IWorkflowExecuteAdditionalData,
|
|
1603
|
+
mode: WorkflowExecuteMode,
|
|
1604
|
+
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
|
1605
|
+
isTest?: boolean,
|
|
1606
|
+
): string | undefined {
|
|
1607
|
+
let baseUrl = additionalData.webhookBaseUrl;
|
|
1608
|
+
if (isTest === true) {
|
|
1609
|
+
baseUrl = additionalData.webhookTestBaseUrl;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
1613
|
+
const webhookDescription = getWebhookDescription(name, workflow, node);
|
|
1614
|
+
if (webhookDescription === undefined) {
|
|
1615
|
+
return undefined;
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
const path = workflow.expression.getSimpleParameterValue(
|
|
1619
|
+
node,
|
|
1620
|
+
webhookDescription.path,
|
|
1621
|
+
mode,
|
|
1622
|
+
additionalKeys,
|
|
1623
|
+
);
|
|
1624
|
+
if (path === undefined) {
|
|
1625
|
+
return undefined;
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
const isFullPath: boolean = workflow.expression.getSimpleParameterValue(
|
|
1629
|
+
node,
|
|
1630
|
+
webhookDescription.isFullPath,
|
|
1631
|
+
mode,
|
|
1632
|
+
additionalKeys,
|
|
1633
|
+
false,
|
|
1634
|
+
) as boolean;
|
|
1635
|
+
return NodeHelpers.getNodeWebhookUrl(baseUrl, workflow.id!, node, path.toString(), isFullPath);
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
/**
|
|
1639
|
+
* Returns the timezone for the workflow
|
|
1640
|
+
*
|
|
1641
|
+
* @export
|
|
1642
|
+
* @param {Workflow} workflow
|
|
1643
|
+
* @param {IWorkflowExecuteAdditionalData} additionalData
|
|
1644
|
+
* @returns {string}
|
|
1645
|
+
*/
|
|
1646
|
+
export function getTimezone(
|
|
1647
|
+
workflow: Workflow,
|
|
1648
|
+
additionalData: IWorkflowExecuteAdditionalData,
|
|
1649
|
+
): string {
|
|
1650
|
+
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
|
|
1651
|
+
if (workflow.settings !== undefined && workflow.settings['timezone'] !== undefined) {
|
|
1652
|
+
return (workflow.settings as IWorkflowSettings).timezone as string;
|
|
1653
|
+
}
|
|
1654
|
+
return additionalData.timezone;
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
/**
|
|
1658
|
+
* Returns the full webhook description of the webhook with the given name
|
|
1659
|
+
*
|
|
1660
|
+
* @export
|
|
1661
|
+
* @param {string} name
|
|
1662
|
+
* @param {Workflow} workflow
|
|
1663
|
+
* @param {INode} node
|
|
1664
|
+
* @returns {(IWebhookDescription | undefined)}
|
|
1665
|
+
*/
|
|
1666
|
+
export function getWebhookDescription(
|
|
1667
|
+
name: string,
|
|
1668
|
+
workflow: Workflow,
|
|
1669
|
+
node: INode,
|
|
1670
|
+
): IWebhookDescription | undefined {
|
|
1671
|
+
const nodeType = workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion) as INodeType;
|
|
1672
|
+
|
|
1673
|
+
if (nodeType.description.webhooks === undefined) {
|
|
1674
|
+
// Node does not have any webhooks so return
|
|
1675
|
+
return undefined;
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
1679
|
+
for (const webhookDescription of nodeType.description.webhooks) {
|
|
1680
|
+
if (webhookDescription.name === name) {
|
|
1681
|
+
return webhookDescription;
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
return undefined;
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
/**
|
|
1689
|
+
* Returns the workflow metadata
|
|
1690
|
+
*
|
|
1691
|
+
* @export
|
|
1692
|
+
* @param {Workflow} workflow
|
|
1693
|
+
* @returns {IWorkflowMetadata}
|
|
1694
|
+
*/
|
|
1695
|
+
export function getWorkflowMetadata(workflow: Workflow): IWorkflowMetadata {
|
|
1696
|
+
return {
|
|
1697
|
+
id: workflow.id,
|
|
1698
|
+
name: workflow.name,
|
|
1699
|
+
active: workflow.active,
|
|
1700
|
+
};
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
/**
|
|
1704
|
+
* Returns the execute functions the poll nodes have access to.
|
|
1705
|
+
*
|
|
1706
|
+
* @export
|
|
1707
|
+
* @param {Workflow} workflow
|
|
1708
|
+
* @param {INode} node
|
|
1709
|
+
* @param {IWorkflowExecuteAdditionalData} additionalData
|
|
1710
|
+
* @param {WorkflowExecuteMode} mode
|
|
1711
|
+
* @returns {ITriggerFunctions}
|
|
1712
|
+
*/
|
|
1713
|
+
// TODO: Check if I can get rid of: additionalData, and so then maybe also at ActiveWorkflowRunner.add
|
|
1714
|
+
export function getExecutePollFunctions(
|
|
1715
|
+
workflow: Workflow,
|
|
1716
|
+
node: INode,
|
|
1717
|
+
additionalData: IWorkflowExecuteAdditionalData,
|
|
1718
|
+
mode: WorkflowExecuteMode,
|
|
1719
|
+
activation: WorkflowActivateMode,
|
|
1720
|
+
): IPollFunctions {
|
|
1721
|
+
return ((workflow: Workflow, node: INode) => {
|
|
1722
|
+
return {
|
|
1723
|
+
__emit: (data: INodeExecutionData[][]): void => {
|
|
1724
|
+
throw new Error('Overwrite NodeExecuteFunctions.getExecutePullFunctions.__emit function!');
|
|
1725
|
+
},
|
|
1726
|
+
async getCredentials(type: string): Promise<ICredentialDataDecryptedObject | undefined> {
|
|
1727
|
+
return getCredentials(workflow, node, type, additionalData, mode);
|
|
1728
|
+
},
|
|
1729
|
+
getMode: (): WorkflowExecuteMode => {
|
|
1730
|
+
return mode;
|
|
1731
|
+
},
|
|
1732
|
+
getActivationMode: (): WorkflowActivateMode => {
|
|
1733
|
+
return activation;
|
|
1734
|
+
},
|
|
1735
|
+
getNode: () => {
|
|
1736
|
+
return getNode(node);
|
|
1737
|
+
},
|
|
1738
|
+
getNodeParameter: (
|
|
1739
|
+
parameterName: string,
|
|
1740
|
+
fallbackValue?: any,
|
|
1741
|
+
):
|
|
1742
|
+
| NodeParameterValue
|
|
1743
|
+
| INodeParameters
|
|
1744
|
+
| NodeParameterValue[]
|
|
1745
|
+
| INodeParameters[]
|
|
1746
|
+
| object => {
|
|
1747
|
+
const runExecutionData: IRunExecutionData | null = null;
|
|
1748
|
+
const itemIndex = 0;
|
|
1749
|
+
const runIndex = 0;
|
|
1750
|
+
const connectionInputData: INodeExecutionData[] = [];
|
|
1751
|
+
|
|
1752
|
+
return getNodeParameter(
|
|
1753
|
+
workflow,
|
|
1754
|
+
runExecutionData,
|
|
1755
|
+
runIndex,
|
|
1756
|
+
connectionInputData,
|
|
1757
|
+
node,
|
|
1758
|
+
parameterName,
|
|
1759
|
+
itemIndex,
|
|
1760
|
+
mode,
|
|
1761
|
+
getAdditionalKeys(additionalData),
|
|
1762
|
+
fallbackValue,
|
|
1763
|
+
);
|
|
1764
|
+
},
|
|
1765
|
+
getRestApiUrl: (): string => {
|
|
1766
|
+
return additionalData.restApiUrl;
|
|
1767
|
+
},
|
|
1768
|
+
getTimezone: (): string => {
|
|
1769
|
+
return getTimezone(workflow, additionalData);
|
|
1770
|
+
},
|
|
1771
|
+
getWorkflow: () => {
|
|
1772
|
+
return getWorkflowMetadata(workflow);
|
|
1773
|
+
},
|
|
1774
|
+
getWorkflowStaticData(type: string): IDataObject {
|
|
1775
|
+
return workflow.getStaticData(type, node);
|
|
1776
|
+
},
|
|
1777
|
+
helpers: {
|
|
1778
|
+
httpRequest,
|
|
1779
|
+
async prepareBinaryData(
|
|
1780
|
+
binaryData: Buffer,
|
|
1781
|
+
filePath?: string,
|
|
1782
|
+
mimeType?: string,
|
|
1783
|
+
): Promise<IBinaryData> {
|
|
1784
|
+
return prepareBinaryData.call(
|
|
1785
|
+
this,
|
|
1786
|
+
binaryData,
|
|
1787
|
+
additionalData.executionId!,
|
|
1788
|
+
filePath,
|
|
1789
|
+
mimeType,
|
|
1790
|
+
);
|
|
1791
|
+
},
|
|
1792
|
+
request: proxyRequestToAxios,
|
|
1793
|
+
async requestWithAuthentication(
|
|
1794
|
+
this: IAllExecuteFunctions,
|
|
1795
|
+
credentialsType: string,
|
|
1796
|
+
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
|
1797
|
+
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
|
1798
|
+
): Promise<any> {
|
|
1799
|
+
return requestWithAuthentication.call(
|
|
1800
|
+
this,
|
|
1801
|
+
credentialsType,
|
|
1802
|
+
requestOptions,
|
|
1803
|
+
workflow,
|
|
1804
|
+
node,
|
|
1805
|
+
additionalData,
|
|
1806
|
+
additionalCredentialOptions,
|
|
1807
|
+
);
|
|
1808
|
+
},
|
|
1809
|
+
async requestOAuth2(
|
|
1810
|
+
this: IAllExecuteFunctions,
|
|
1811
|
+
credentialsType: string,
|
|
1812
|
+
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
|
1813
|
+
oAuth2Options?: IOAuth2Options,
|
|
1814
|
+
): Promise<any> {
|
|
1815
|
+
return requestOAuth2.call(
|
|
1816
|
+
this,
|
|
1817
|
+
credentialsType,
|
|
1818
|
+
requestOptions,
|
|
1819
|
+
node,
|
|
1820
|
+
additionalData,
|
|
1821
|
+
oAuth2Options,
|
|
1822
|
+
);
|
|
1823
|
+
},
|
|
1824
|
+
async getCurrentOAuth2AccessToken(
|
|
1825
|
+
this: IAllExecuteFunctions,
|
|
1826
|
+
credentialsType: string,
|
|
1827
|
+
oAuth2Options?: IOAuth2Options,
|
|
1828
|
+
): Promise<any> {
|
|
1829
|
+
return getCurrentOAuth2AccessToken.call(
|
|
1830
|
+
this,
|
|
1831
|
+
credentialsType,
|
|
1832
|
+
node,
|
|
1833
|
+
additionalData,
|
|
1834
|
+
oAuth2Options,
|
|
1835
|
+
);
|
|
1836
|
+
},
|
|
1837
|
+
async requestOAuth1(
|
|
1838
|
+
this: IAllExecuteFunctions,
|
|
1839
|
+
credentialsType: string,
|
|
1840
|
+
requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions,
|
|
1841
|
+
): Promise<any> {
|
|
1842
|
+
return requestOAuth1.call(this, credentialsType, requestOptions);
|
|
1843
|
+
},
|
|
1844
|
+
async httpRequestWithAuthentication(
|
|
1845
|
+
this: IAllExecuteFunctions,
|
|
1846
|
+
credentialsType: string,
|
|
1847
|
+
requestOptions: IHttpRequestOptions,
|
|
1848
|
+
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
|
1849
|
+
): Promise<any> {
|
|
1850
|
+
return httpRequestWithAuthentication.call(
|
|
1851
|
+
this,
|
|
1852
|
+
credentialsType,
|
|
1853
|
+
requestOptions,
|
|
1854
|
+
workflow,
|
|
1855
|
+
node,
|
|
1856
|
+
additionalData,
|
|
1857
|
+
additionalCredentialOptions,
|
|
1858
|
+
);
|
|
1859
|
+
},
|
|
1860
|
+
returnJsonArray,
|
|
1861
|
+
},
|
|
1862
|
+
};
|
|
1863
|
+
})(workflow, node);
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
/**
|
|
1867
|
+
* Returns the execute functions the trigger nodes have access to.
|
|
1868
|
+
*
|
|
1869
|
+
* @export
|
|
1870
|
+
* @param {Workflow} workflow
|
|
1871
|
+
* @param {INode} node
|
|
1872
|
+
* @param {IWorkflowExecuteAdditionalData} additionalData
|
|
1873
|
+
* @param {WorkflowExecuteMode} mode
|
|
1874
|
+
* @returns {ITriggerFunctions}
|
|
1875
|
+
*/
|
|
1876
|
+
// TODO: Check if I can get rid of: additionalData, and so then maybe also at ActiveWorkflowRunner.add
|
|
1877
|
+
export function getExecuteTriggerFunctions(
|
|
1878
|
+
workflow: Workflow,
|
|
1879
|
+
node: INode,
|
|
1880
|
+
additionalData: IWorkflowExecuteAdditionalData,
|
|
1881
|
+
mode: WorkflowExecuteMode,
|
|
1882
|
+
activation: WorkflowActivateMode,
|
|
1883
|
+
): ITriggerFunctions {
|
|
1884
|
+
return ((workflow: Workflow, node: INode) => {
|
|
1885
|
+
return {
|
|
1886
|
+
emit: (data: INodeExecutionData[][]): void => {
|
|
1887
|
+
throw new Error('Overwrite NodeExecuteFunctions.getExecuteTriggerFunctions.emit function!');
|
|
1888
|
+
},
|
|
1889
|
+
async getCredentials(type: string): Promise<ICredentialDataDecryptedObject | undefined> {
|
|
1890
|
+
return getCredentials(workflow, node, type, additionalData, mode);
|
|
1891
|
+
},
|
|
1892
|
+
getNode: () => {
|
|
1893
|
+
return getNode(node);
|
|
1894
|
+
},
|
|
1895
|
+
getMode: (): WorkflowExecuteMode => {
|
|
1896
|
+
return mode;
|
|
1897
|
+
},
|
|
1898
|
+
getActivationMode: (): WorkflowActivateMode => {
|
|
1899
|
+
return activation;
|
|
1900
|
+
},
|
|
1901
|
+
getNodeParameter: (
|
|
1902
|
+
parameterName: string,
|
|
1903
|
+
fallbackValue?: any,
|
|
1904
|
+
):
|
|
1905
|
+
| NodeParameterValue
|
|
1906
|
+
| INodeParameters
|
|
1907
|
+
| NodeParameterValue[]
|
|
1908
|
+
| INodeParameters[]
|
|
1909
|
+
| object => {
|
|
1910
|
+
const runExecutionData: IRunExecutionData | null = null;
|
|
1911
|
+
const itemIndex = 0;
|
|
1912
|
+
const runIndex = 0;
|
|
1913
|
+
const connectionInputData: INodeExecutionData[] = [];
|
|
1914
|
+
|
|
1915
|
+
return getNodeParameter(
|
|
1916
|
+
workflow,
|
|
1917
|
+
runExecutionData,
|
|
1918
|
+
runIndex,
|
|
1919
|
+
connectionInputData,
|
|
1920
|
+
node,
|
|
1921
|
+
parameterName,
|
|
1922
|
+
itemIndex,
|
|
1923
|
+
mode,
|
|
1924
|
+
getAdditionalKeys(additionalData),
|
|
1925
|
+
fallbackValue,
|
|
1926
|
+
);
|
|
1927
|
+
},
|
|
1928
|
+
getRestApiUrl: (): string => {
|
|
1929
|
+
return additionalData.restApiUrl;
|
|
1930
|
+
},
|
|
1931
|
+
getTimezone: (): string => {
|
|
1932
|
+
return getTimezone(workflow, additionalData);
|
|
1933
|
+
},
|
|
1934
|
+
getWorkflow: () => {
|
|
1935
|
+
return getWorkflowMetadata(workflow);
|
|
1936
|
+
},
|
|
1937
|
+
getWorkflowStaticData(type: string): IDataObject {
|
|
1938
|
+
return workflow.getStaticData(type, node);
|
|
1939
|
+
},
|
|
1940
|
+
helpers: {
|
|
1941
|
+
httpRequest,
|
|
1942
|
+
async requestWithAuthentication(
|
|
1943
|
+
this: IAllExecuteFunctions,
|
|
1944
|
+
credentialsType: string,
|
|
1945
|
+
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
|
1946
|
+
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
|
1947
|
+
): Promise<any> {
|
|
1948
|
+
return requestWithAuthentication.call(
|
|
1949
|
+
this,
|
|
1950
|
+
credentialsType,
|
|
1951
|
+
requestOptions,
|
|
1952
|
+
workflow,
|
|
1953
|
+
node,
|
|
1954
|
+
additionalData,
|
|
1955
|
+
additionalCredentialOptions,
|
|
1956
|
+
);
|
|
1957
|
+
},
|
|
1958
|
+
async prepareBinaryData(
|
|
1959
|
+
binaryData: Buffer,
|
|
1960
|
+
filePath?: string,
|
|
1961
|
+
mimeType?: string,
|
|
1962
|
+
): Promise<IBinaryData> {
|
|
1963
|
+
return prepareBinaryData.call(
|
|
1964
|
+
this,
|
|
1965
|
+
binaryData,
|
|
1966
|
+
additionalData.executionId!,
|
|
1967
|
+
filePath,
|
|
1968
|
+
mimeType,
|
|
1969
|
+
);
|
|
1970
|
+
},
|
|
1971
|
+
request: proxyRequestToAxios,
|
|
1972
|
+
async requestOAuth2(
|
|
1973
|
+
this: IAllExecuteFunctions,
|
|
1974
|
+
credentialsType: string,
|
|
1975
|
+
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
|
1976
|
+
oAuth2Options?: IOAuth2Options,
|
|
1977
|
+
): Promise<any> {
|
|
1978
|
+
return requestOAuth2.call(
|
|
1979
|
+
this,
|
|
1980
|
+
credentialsType,
|
|
1981
|
+
requestOptions,
|
|
1982
|
+
node,
|
|
1983
|
+
additionalData,
|
|
1984
|
+
oAuth2Options,
|
|
1985
|
+
);
|
|
1986
|
+
},
|
|
1987
|
+
async requestOAuth1(
|
|
1988
|
+
this: IAllExecuteFunctions,
|
|
1989
|
+
credentialsType: string,
|
|
1990
|
+
requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions,
|
|
1991
|
+
): Promise<any> {
|
|
1992
|
+
return requestOAuth1.call(this, credentialsType, requestOptions);
|
|
1993
|
+
},
|
|
1994
|
+
async httpRequestWithAuthentication(
|
|
1995
|
+
this: IAllExecuteFunctions,
|
|
1996
|
+
credentialsType: string,
|
|
1997
|
+
requestOptions: IHttpRequestOptions,
|
|
1998
|
+
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
|
1999
|
+
): Promise<any> {
|
|
2000
|
+
return httpRequestWithAuthentication.call(
|
|
2001
|
+
this,
|
|
2002
|
+
credentialsType,
|
|
2003
|
+
requestOptions,
|
|
2004
|
+
workflow,
|
|
2005
|
+
node,
|
|
2006
|
+
additionalData,
|
|
2007
|
+
additionalCredentialOptions,
|
|
2008
|
+
);
|
|
2009
|
+
},
|
|
2010
|
+
returnJsonArray,
|
|
2011
|
+
},
|
|
2012
|
+
};
|
|
2013
|
+
})(workflow, node);
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
/**
|
|
2017
|
+
* Returns the execute functions regular nodes have access to.
|
|
2018
|
+
*
|
|
2019
|
+
* @export
|
|
2020
|
+
* @param {Workflow} workflow
|
|
2021
|
+
* @param {IRunExecutionData} runExecutionData
|
|
2022
|
+
* @param {number} runIndex
|
|
2023
|
+
* @param {INodeExecutionData[]} connectionInputData
|
|
2024
|
+
* @param {ITaskDataConnections} inputData
|
|
2025
|
+
* @param {INode} node
|
|
2026
|
+
* @param {IWorkflowExecuteAdditionalData} additionalData
|
|
2027
|
+
* @param {WorkflowExecuteMode} mode
|
|
2028
|
+
* @returns {IExecuteFunctions}
|
|
2029
|
+
*/
|
|
2030
|
+
export function getExecuteFunctions(
|
|
2031
|
+
workflow: Workflow,
|
|
2032
|
+
runExecutionData: IRunExecutionData,
|
|
2033
|
+
runIndex: number,
|
|
2034
|
+
connectionInputData: INodeExecutionData[],
|
|
2035
|
+
inputData: ITaskDataConnections,
|
|
2036
|
+
node: INode,
|
|
2037
|
+
additionalData: IWorkflowExecuteAdditionalData,
|
|
2038
|
+
mode: WorkflowExecuteMode,
|
|
2039
|
+
nodeTypeData: INodeType,
|
|
2040
|
+
closeFunctions: CloseFunction[],
|
|
2041
|
+
): IExecuteFunctions {
|
|
2042
|
+
return ((workflow, runExecutionData, connectionInputData, inputData, node, nodeTypeData, closeFunctions) => {
|
|
2043
|
+
return {
|
|
2044
|
+
continueOnFail: () => {
|
|
2045
|
+
return continueOnFail(node);
|
|
2046
|
+
},
|
|
2047
|
+
evaluateExpression: (expression: string, itemIndex: number) => {
|
|
2048
|
+
return workflow.expression.resolveSimpleParameterValue(
|
|
2049
|
+
`=${expression}`,
|
|
2050
|
+
{},
|
|
2051
|
+
runExecutionData,
|
|
2052
|
+
runIndex,
|
|
2053
|
+
itemIndex,
|
|
2054
|
+
node.name,
|
|
2055
|
+
connectionInputData,
|
|
2056
|
+
mode,
|
|
2057
|
+
getAdditionalKeys(additionalData),
|
|
2058
|
+
);
|
|
2059
|
+
},
|
|
2060
|
+
async executeWorkflow(
|
|
2061
|
+
workflowInfo: IExecuteWorkflowInfo,
|
|
2062
|
+
inputData?: INodeExecutionData[],
|
|
2063
|
+
): Promise<any> {
|
|
2064
|
+
return additionalData
|
|
2065
|
+
.executeWorkflow(workflowInfo, additionalData, inputData)
|
|
2066
|
+
.then(async (result) =>
|
|
2067
|
+
BinaryDataManager.getInstance().duplicateBinaryData(
|
|
2068
|
+
result,
|
|
2069
|
+
additionalData.executionId!,
|
|
2070
|
+
),
|
|
2071
|
+
);
|
|
2072
|
+
},
|
|
2073
|
+
getContext(type: string): IContextObject {
|
|
2074
|
+
return NodeHelpers.getContext(runExecutionData, type, node);
|
|
2075
|
+
},
|
|
2076
|
+
async getCredentials(
|
|
2077
|
+
type: string,
|
|
2078
|
+
itemIndex?: number,
|
|
2079
|
+
): Promise<ICredentialDataDecryptedObject | undefined> {
|
|
2080
|
+
return getCredentials(
|
|
2081
|
+
workflow,
|
|
2082
|
+
node,
|
|
2083
|
+
type,
|
|
2084
|
+
additionalData,
|
|
2085
|
+
mode,
|
|
2086
|
+
runExecutionData,
|
|
2087
|
+
runIndex,
|
|
2088
|
+
connectionInputData,
|
|
2089
|
+
itemIndex,
|
|
2090
|
+
);
|
|
2091
|
+
},
|
|
2092
|
+
getExecutionId: (): string => {
|
|
2093
|
+
return additionalData.executionId!;
|
|
2094
|
+
},
|
|
2095
|
+
getInputData: (inputIndex = 0, inputName = 'main') => {
|
|
2096
|
+
if (!Object.prototype.hasOwnProperty.call(inputData, inputName)) {
|
|
2097
|
+
// Return empty array because else it would throw error when nothing is connected to input
|
|
2098
|
+
return [];
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
// TODO: Check if nodeType has input with that index defined
|
|
2102
|
+
if (inputData[inputName].length < inputIndex) {
|
|
2103
|
+
throw new Error(`Could not get input index "${inputIndex}" of input "${inputName}"!`);
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
if (inputData[inputName][inputIndex] === null) {
|
|
2107
|
+
// return [];
|
|
2108
|
+
throw new Error(`Value "${inputIndex}" of input "${inputName}" did not get set!`);
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
return inputData[inputName][inputIndex] as INodeExecutionData[];
|
|
2112
|
+
},
|
|
2113
|
+
getNodeParameter: (
|
|
2114
|
+
parameterName: string,
|
|
2115
|
+
itemIndex: number,
|
|
2116
|
+
fallbackValue?: any,
|
|
2117
|
+
):
|
|
2118
|
+
| NodeParameterValue
|
|
2119
|
+
| INodeParameters
|
|
2120
|
+
| NodeParameterValue[]
|
|
2121
|
+
| INodeParameters[]
|
|
2122
|
+
| object => {
|
|
2123
|
+
return getNodeParameter(
|
|
2124
|
+
workflow,
|
|
2125
|
+
runExecutionData,
|
|
2126
|
+
runIndex,
|
|
2127
|
+
connectionInputData,
|
|
2128
|
+
node,
|
|
2129
|
+
parameterName,
|
|
2130
|
+
itemIndex,
|
|
2131
|
+
mode,
|
|
2132
|
+
getAdditionalKeys(additionalData),
|
|
2133
|
+
fallbackValue,
|
|
2134
|
+
);
|
|
2135
|
+
},
|
|
2136
|
+
getMode: (): WorkflowExecuteMode => {
|
|
2137
|
+
return mode;
|
|
2138
|
+
},
|
|
2139
|
+
getNode: () => {
|
|
2140
|
+
return getNode(node);
|
|
2141
|
+
},
|
|
2142
|
+
getRestApiUrl: (): string => {
|
|
2143
|
+
return additionalData.restApiUrl;
|
|
2144
|
+
},
|
|
2145
|
+
getTimezone: (): string => {
|
|
2146
|
+
return getTimezone(workflow, additionalData);
|
|
2147
|
+
},
|
|
2148
|
+
getWorkflow: () => {
|
|
2149
|
+
return getWorkflowMetadata(workflow);
|
|
2150
|
+
},
|
|
2151
|
+
getWorkflowDataProxy: (itemIndex: number): IWorkflowDataProxyData => {
|
|
2152
|
+
const dataProxy = new WorkflowDataProxy(
|
|
2153
|
+
workflow,
|
|
2154
|
+
runExecutionData,
|
|
2155
|
+
runIndex,
|
|
2156
|
+
itemIndex,
|
|
2157
|
+
node.name,
|
|
2158
|
+
connectionInputData,
|
|
2159
|
+
{},
|
|
2160
|
+
mode,
|
|
2161
|
+
getAdditionalKeys(additionalData),
|
|
2162
|
+
);
|
|
2163
|
+
return dataProxy.getDataProxy();
|
|
2164
|
+
},
|
|
2165
|
+
getWorkflowStaticData(type: string): IDataObject {
|
|
2166
|
+
return workflow.getStaticData(type, node);
|
|
2167
|
+
},
|
|
2168
|
+
prepareOutputData: NodeHelpers.prepareOutputData,
|
|
2169
|
+
async putExecutionToWait(waitTill: Date): Promise<void> {
|
|
2170
|
+
runExecutionData.waitTill = waitTill;
|
|
2171
|
+
},
|
|
2172
|
+
sendMessageToUI(...args: any[]): void {
|
|
2173
|
+
if (mode !== 'manual') {
|
|
2174
|
+
return;
|
|
2175
|
+
}
|
|
2176
|
+
try {
|
|
2177
|
+
if (additionalData.sendMessageToUI) {
|
|
2178
|
+
additionalData.sendMessageToUI(node.name, args);
|
|
2179
|
+
}
|
|
2180
|
+
} catch (error: any) {
|
|
2181
|
+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
2182
|
+
Logger.warn(`There was a problem sending messsage to UI: ${error.message}`);
|
|
2183
|
+
}
|
|
2184
|
+
},
|
|
2185
|
+
async sendResponse(response: IExecuteResponsePromiseData): Promise<void> {
|
|
2186
|
+
await additionalData.hooks?.executeHookFunctions('sendResponse', [response]);
|
|
2187
|
+
},
|
|
2188
|
+
helpers: {
|
|
2189
|
+
httpRequest,
|
|
2190
|
+
async requestWithAuthentication(
|
|
2191
|
+
this: IAllExecuteFunctions,
|
|
2192
|
+
credentialsType: string,
|
|
2193
|
+
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
|
2194
|
+
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
|
2195
|
+
): Promise<any> {
|
|
2196
|
+
return requestWithAuthentication.call(
|
|
2197
|
+
this,
|
|
2198
|
+
credentialsType,
|
|
2199
|
+
requestOptions,
|
|
2200
|
+
workflow,
|
|
2201
|
+
node,
|
|
2202
|
+
additionalData,
|
|
2203
|
+
additionalCredentialOptions,
|
|
2204
|
+
);
|
|
2205
|
+
},
|
|
2206
|
+
assertBinaryData(
|
|
2207
|
+
itemIndex: number,
|
|
2208
|
+
propertyName: string,
|
|
2209
|
+
inputIndex: number = 0,
|
|
2210
|
+
): IBinaryData {
|
|
2211
|
+
const binaryKeyData = inputData['main'][inputIndex]![itemIndex].binary;
|
|
2212
|
+
if (binaryKeyData === undefined) {
|
|
2213
|
+
throw new NodeOperationError(
|
|
2214
|
+
node,
|
|
2215
|
+
`This operation expects the node's input data to contain a binary file '${propertyName}', but none was found [item ${itemIndex}]`,
|
|
2216
|
+
{
|
|
2217
|
+
itemIndex,
|
|
2218
|
+
description: 'Make sure that the previous node outputs a binary file',
|
|
2219
|
+
},
|
|
2220
|
+
);
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
const binaryPropertyData = binaryKeyData[propertyName];
|
|
2224
|
+
if (binaryPropertyData === undefined) {
|
|
2225
|
+
throw new NodeOperationError(
|
|
2226
|
+
node,
|
|
2227
|
+
`The item has no binary field '${propertyName}' [item ${itemIndex}]`,
|
|
2228
|
+
{
|
|
2229
|
+
itemIndex,
|
|
2230
|
+
description:
|
|
2231
|
+
'Check that the parameter where you specified the input binary field name is correct, and that it matches a field in the binary input',
|
|
2232
|
+
},
|
|
2233
|
+
);
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
return binaryPropertyData;
|
|
2237
|
+
},
|
|
2238
|
+
|
|
2239
|
+
async prepareBinaryData(
|
|
2240
|
+
binaryData: Buffer,
|
|
2241
|
+
filePath?: string,
|
|
2242
|
+
mimeType?: string,
|
|
2243
|
+
): Promise<IBinaryData> {
|
|
2244
|
+
return prepareBinaryData.call(
|
|
2245
|
+
this,
|
|
2246
|
+
binaryData,
|
|
2247
|
+
additionalData.executionId!,
|
|
2248
|
+
filePath,
|
|
2249
|
+
mimeType,
|
|
2250
|
+
);
|
|
2251
|
+
},
|
|
2252
|
+
async getBinaryDataBuffer(
|
|
2253
|
+
itemIndex: number,
|
|
2254
|
+
propertyName: string,
|
|
2255
|
+
inputIndex = 0,
|
|
2256
|
+
): Promise<Buffer> {
|
|
2257
|
+
return getBinaryDataBuffer.call(this, inputData, itemIndex, propertyName, inputIndex);
|
|
2258
|
+
},
|
|
2259
|
+
request: proxyRequestToAxios,
|
|
2260
|
+
async requestOAuth2(
|
|
2261
|
+
this: IAllExecuteFunctions,
|
|
2262
|
+
credentialsType: string,
|
|
2263
|
+
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
|
2264
|
+
oAuth2Options?: IOAuth2Options,
|
|
2265
|
+
): Promise<any> {
|
|
2266
|
+
return requestOAuth2.call(
|
|
2267
|
+
this,
|
|
2268
|
+
credentialsType,
|
|
2269
|
+
requestOptions,
|
|
2270
|
+
node,
|
|
2271
|
+
additionalData,
|
|
2272
|
+
oAuth2Options,
|
|
2273
|
+
);
|
|
2274
|
+
},
|
|
2275
|
+
async requestOAuth1(
|
|
2276
|
+
this: IAllExecuteFunctions,
|
|
2277
|
+
credentialsType: string,
|
|
2278
|
+
requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions,
|
|
2279
|
+
): Promise<any> {
|
|
2280
|
+
return requestOAuth1.call(this, credentialsType, requestOptions);
|
|
2281
|
+
},
|
|
2282
|
+
async httpRequestWithAuthentication(
|
|
2283
|
+
this: IAllExecuteFunctions,
|
|
2284
|
+
credentialsType: string,
|
|
2285
|
+
requestOptions: IHttpRequestOptions,
|
|
2286
|
+
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
|
2287
|
+
): Promise<any> {
|
|
2288
|
+
return httpRequestWithAuthentication.call(
|
|
2289
|
+
this,
|
|
2290
|
+
credentialsType,
|
|
2291
|
+
requestOptions,
|
|
2292
|
+
workflow,
|
|
2293
|
+
node,
|
|
2294
|
+
additionalData,
|
|
2295
|
+
additionalCredentialOptions,
|
|
2296
|
+
);
|
|
2297
|
+
},
|
|
2298
|
+
returnJsonArray,
|
|
2299
|
+
normalizeItems,
|
|
2300
|
+
},
|
|
2301
|
+
getInstanceId: () => {
|
|
2302
|
+
// Return the node's instance ID if available, otherwise return an empty string
|
|
2303
|
+
return node.id || '';
|
|
2304
|
+
},
|
|
2305
|
+
async getInputConnectionData(
|
|
2306
|
+
this: IExecuteFunctions | ISupplyDataFunctions,
|
|
2307
|
+
connectionType: NodeConnectionType,
|
|
2308
|
+
itemIndex: number,
|
|
2309
|
+
): Promise<unknown> {
|
|
2310
|
+
return await getInputConnectionData.call(
|
|
2311
|
+
this,
|
|
2312
|
+
workflow,
|
|
2313
|
+
runExecutionData,
|
|
2314
|
+
runIndex,
|
|
2315
|
+
connectionInputData,
|
|
2316
|
+
inputData,
|
|
2317
|
+
additionalData,
|
|
2318
|
+
connectionType,
|
|
2319
|
+
mode,
|
|
2320
|
+
itemIndex,
|
|
2321
|
+
nodeTypeData,
|
|
2322
|
+
closeFunctions,
|
|
2323
|
+
);
|
|
2324
|
+
},
|
|
2325
|
+
|
|
2326
|
+
addInputData(
|
|
2327
|
+
data: INodeExecutionData[][] | ExecutionError,
|
|
2328
|
+
node: INode
|
|
2329
|
+
): { index: number, inputExecutionData: IRunExecutionData } {
|
|
2330
|
+
const nodeName = node.name;
|
|
2331
|
+
let currentNodeRunIndex = 0;
|
|
2332
|
+
runExecutionData = {
|
|
2333
|
+
startData: {
|
|
2334
|
+
destinationNode: nodeName,
|
|
2335
|
+
},
|
|
2336
|
+
resultData: {
|
|
2337
|
+
runData: {
|
|
2338
|
+
[nodeName]: [JSON.parse(JSON.stringify(data))]
|
|
2339
|
+
},
|
|
2340
|
+
lastNodeExecuted: nodeName,
|
|
2341
|
+
},
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
return { index: currentNodeRunIndex, inputExecutionData: runExecutionData };
|
|
2345
|
+
},
|
|
2346
|
+
addOutputData(
|
|
2347
|
+
data: INodeExecutionData[][] | ExecutionError,
|
|
2348
|
+
node: INode
|
|
2349
|
+
): { outputExecutionData: IRunExecutionData } {
|
|
2350
|
+
const nodeName = node.name;
|
|
2351
|
+
const outputExecutionData = {
|
|
2352
|
+
startData: {
|
|
2353
|
+
destinationNode: nodeName,
|
|
2354
|
+
},
|
|
2355
|
+
resultData: {
|
|
2356
|
+
error: typeof data === 'object' && 'error' in data ? data.error as ExecutionError : undefined,
|
|
2357
|
+
runData: {
|
|
2358
|
+
[nodeName]: [JSON.parse(JSON.stringify(data))]
|
|
2359
|
+
},
|
|
2360
|
+
lastNodeExecuted: nodeName,
|
|
2361
|
+
},
|
|
2362
|
+
}
|
|
2363
|
+
return { outputExecutionData };
|
|
2364
|
+
|
|
2365
|
+
},
|
|
2366
|
+
getExecutionCancelSignal: () => {
|
|
2367
|
+
return undefined;
|
|
2368
|
+
},
|
|
2369
|
+
};
|
|
2370
|
+
|
|
2371
|
+
|
|
2372
|
+
})(workflow, runExecutionData, connectionInputData, inputData, node, nodeTypeData, closeFunctions) as unknown as IExecuteFunctions;
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
/**
|
|
2376
|
+
* Returns the execute functions regular nodes have access to when single-function is defined.
|
|
2377
|
+
*
|
|
2378
|
+
* @export
|
|
2379
|
+
* @param {Workflow} workflow
|
|
2380
|
+
* @param {IRunExecutionData} runExecutionData
|
|
2381
|
+
* @param {number} runIndex
|
|
2382
|
+
* @param {INodeExecutionData[]} connectionInputData
|
|
2383
|
+
* @param {ITaskDataConnections} inputData
|
|
2384
|
+
* @param {INode} node
|
|
2385
|
+
* @param {number} itemIndex
|
|
2386
|
+
* @param {IWorkflowExecuteAdditionalData} additionalData
|
|
2387
|
+
* @param {WorkflowExecuteMode} mode
|
|
2388
|
+
* @returns {IExecuteSingleFunctions}
|
|
2389
|
+
*/
|
|
2390
|
+
export function getExecuteSingleFunctions(
|
|
2391
|
+
workflow: Workflow,
|
|
2392
|
+
runExecutionData: IRunExecutionData,
|
|
2393
|
+
runIndex: number,
|
|
2394
|
+
connectionInputData: INodeExecutionData[],
|
|
2395
|
+
inputData: ITaskDataConnections,
|
|
2396
|
+
node: INode,
|
|
2397
|
+
itemIndex: number,
|
|
2398
|
+
additionalData: IWorkflowExecuteAdditionalData,
|
|
2399
|
+
mode: WorkflowExecuteMode,
|
|
2400
|
+
): IExecuteSingleFunctions {
|
|
2401
|
+
return ((workflow, runExecutionData, connectionInputData, inputData, node, itemIndex) => {
|
|
2402
|
+
return {
|
|
2403
|
+
continueOnFail: () => {
|
|
2404
|
+
return continueOnFail(node);
|
|
2405
|
+
},
|
|
2406
|
+
evaluateExpression: (expression: string, evaluateItemIndex: number | undefined) => {
|
|
2407
|
+
evaluateItemIndex = evaluateItemIndex === undefined ? itemIndex : evaluateItemIndex;
|
|
2408
|
+
return workflow.expression.resolveSimpleParameterValue(
|
|
2409
|
+
`=${expression}`,
|
|
2410
|
+
{},
|
|
2411
|
+
runExecutionData,
|
|
2412
|
+
runIndex,
|
|
2413
|
+
evaluateItemIndex,
|
|
2414
|
+
node.name,
|
|
2415
|
+
connectionInputData,
|
|
2416
|
+
mode,
|
|
2417
|
+
getAdditionalKeys(additionalData),
|
|
2418
|
+
);
|
|
2419
|
+
},
|
|
2420
|
+
getContext(type: string): IContextObject {
|
|
2421
|
+
return NodeHelpers.getContext(runExecutionData, type, node);
|
|
2422
|
+
},
|
|
2423
|
+
async getCredentials(type: string): Promise<ICredentialDataDecryptedObject | undefined> {
|
|
2424
|
+
return getCredentials(
|
|
2425
|
+
workflow,
|
|
2426
|
+
node,
|
|
2427
|
+
type,
|
|
2428
|
+
additionalData,
|
|
2429
|
+
mode,
|
|
2430
|
+
runExecutionData,
|
|
2431
|
+
runIndex,
|
|
2432
|
+
connectionInputData,
|
|
2433
|
+
itemIndex,
|
|
2434
|
+
);
|
|
2435
|
+
},
|
|
2436
|
+
getInputData: (inputIndex = 0, inputName = 'main') => {
|
|
2437
|
+
if (!Object.prototype.hasOwnProperty.call(inputData, inputName)) {
|
|
2438
|
+
// Return empty array because else it would throw error when nothing is connected to input
|
|
2439
|
+
return { json: {} };
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
// TODO: Check if nodeType has input with that index defined
|
|
2443
|
+
if (inputData[inputName].length < inputIndex) {
|
|
2444
|
+
throw new Error(`Could not get input index "${inputIndex}" of input "${inputName}"!`);
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
const allItems = inputData[inputName][inputIndex];
|
|
2448
|
+
|
|
2449
|
+
if (allItems === null) {
|
|
2450
|
+
// return [];
|
|
2451
|
+
throw new Error(`Value "${inputIndex}" of input "${inputName}" did not get set!`);
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
if (allItems[itemIndex] === null) {
|
|
2455
|
+
// return [];
|
|
2456
|
+
throw new Error(
|
|
2457
|
+
`Value "${inputIndex}" of input "${inputName}" with itemIndex "${itemIndex}" did not get set!`,
|
|
2458
|
+
);
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
return allItems[itemIndex];
|
|
2462
|
+
},
|
|
2463
|
+
getMode: (): WorkflowExecuteMode => {
|
|
2464
|
+
return mode;
|
|
2465
|
+
},
|
|
2466
|
+
getNode: () => {
|
|
2467
|
+
return getNode(node);
|
|
2468
|
+
},
|
|
2469
|
+
getRestApiUrl: (): string => {
|
|
2470
|
+
return additionalData.restApiUrl;
|
|
2471
|
+
},
|
|
2472
|
+
getTimezone: (): string => {
|
|
2473
|
+
return getTimezone(workflow, additionalData);
|
|
2474
|
+
},
|
|
2475
|
+
getNodeParameter: (
|
|
2476
|
+
parameterName: string,
|
|
2477
|
+
fallbackValue?: any,
|
|
2478
|
+
):
|
|
2479
|
+
| NodeParameterValue
|
|
2480
|
+
| INodeParameters
|
|
2481
|
+
| NodeParameterValue[]
|
|
2482
|
+
| INodeParameters[]
|
|
2483
|
+
| object => {
|
|
2484
|
+
return getNodeParameter(
|
|
2485
|
+
workflow,
|
|
2486
|
+
runExecutionData,
|
|
2487
|
+
runIndex,
|
|
2488
|
+
connectionInputData,
|
|
2489
|
+
node,
|
|
2490
|
+
parameterName,
|
|
2491
|
+
itemIndex,
|
|
2492
|
+
mode,
|
|
2493
|
+
getAdditionalKeys(additionalData),
|
|
2494
|
+
fallbackValue,
|
|
2495
|
+
);
|
|
2496
|
+
},
|
|
2497
|
+
getWorkflow: () => {
|
|
2498
|
+
return getWorkflowMetadata(workflow);
|
|
2499
|
+
},
|
|
2500
|
+
getWorkflowDataProxy: (): IWorkflowDataProxyData => {
|
|
2501
|
+
const dataProxy = new WorkflowDataProxy(
|
|
2502
|
+
workflow,
|
|
2503
|
+
runExecutionData,
|
|
2504
|
+
runIndex,
|
|
2505
|
+
itemIndex,
|
|
2506
|
+
node.name,
|
|
2507
|
+
connectionInputData,
|
|
2508
|
+
{},
|
|
2509
|
+
mode,
|
|
2510
|
+
getAdditionalKeys(additionalData),
|
|
2511
|
+
);
|
|
2512
|
+
return dataProxy.getDataProxy();
|
|
2513
|
+
},
|
|
2514
|
+
getWorkflowStaticData(type: string): IDataObject {
|
|
2515
|
+
return workflow.getStaticData(type, node);
|
|
2516
|
+
},
|
|
2517
|
+
helpers: {
|
|
2518
|
+
httpRequest,
|
|
2519
|
+
async requestWithAuthentication(
|
|
2520
|
+
this: IAllExecuteFunctions,
|
|
2521
|
+
credentialsType: string,
|
|
2522
|
+
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
|
2523
|
+
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
|
2524
|
+
): Promise<any> {
|
|
2525
|
+
return requestWithAuthentication.call(
|
|
2526
|
+
this,
|
|
2527
|
+
credentialsType,
|
|
2528
|
+
requestOptions,
|
|
2529
|
+
workflow,
|
|
2530
|
+
node,
|
|
2531
|
+
additionalData,
|
|
2532
|
+
additionalCredentialOptions,
|
|
2533
|
+
);
|
|
2534
|
+
},
|
|
2535
|
+
async prepareBinaryData(
|
|
2536
|
+
binaryData: Buffer,
|
|
2537
|
+
filePath?: string,
|
|
2538
|
+
mimeType?: string,
|
|
2539
|
+
): Promise<IBinaryData> {
|
|
2540
|
+
return prepareBinaryData.call(
|
|
2541
|
+
this,
|
|
2542
|
+
binaryData,
|
|
2543
|
+
additionalData.executionId!,
|
|
2544
|
+
filePath,
|
|
2545
|
+
mimeType,
|
|
2546
|
+
);
|
|
2547
|
+
},
|
|
2548
|
+
request: proxyRequestToAxios,
|
|
2549
|
+
async requestOAuth2(
|
|
2550
|
+
this: IAllExecuteFunctions,
|
|
2551
|
+
credentialsType: string,
|
|
2552
|
+
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
|
2553
|
+
oAuth2Options?: IOAuth2Options,
|
|
2554
|
+
): Promise<any> {
|
|
2555
|
+
return requestOAuth2.call(
|
|
2556
|
+
this,
|
|
2557
|
+
credentialsType,
|
|
2558
|
+
requestOptions,
|
|
2559
|
+
node,
|
|
2560
|
+
additionalData,
|
|
2561
|
+
oAuth2Options,
|
|
2562
|
+
);
|
|
2563
|
+
},
|
|
2564
|
+
async requestOAuth1(
|
|
2565
|
+
this: IAllExecuteFunctions,
|
|
2566
|
+
credentialsType: string,
|
|
2567
|
+
requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions,
|
|
2568
|
+
): Promise<any> {
|
|
2569
|
+
return requestOAuth1.call(this, credentialsType, requestOptions);
|
|
2570
|
+
},
|
|
2571
|
+
async httpRequestWithAuthentication(
|
|
2572
|
+
this: IAllExecuteFunctions,
|
|
2573
|
+
credentialsType: string,
|
|
2574
|
+
requestOptions: IHttpRequestOptions,
|
|
2575
|
+
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
|
2576
|
+
): Promise<any> {
|
|
2577
|
+
return httpRequestWithAuthentication.call(
|
|
2578
|
+
this,
|
|
2579
|
+
credentialsType,
|
|
2580
|
+
requestOptions,
|
|
2581
|
+
workflow,
|
|
2582
|
+
node,
|
|
2583
|
+
additionalData,
|
|
2584
|
+
additionalCredentialOptions,
|
|
2585
|
+
);
|
|
2586
|
+
},
|
|
2587
|
+
},
|
|
2588
|
+
getInstanceId: () => {
|
|
2589
|
+
// Return the node's instance ID if available, otherwise return an empty string
|
|
2590
|
+
return node.id || '';
|
|
2591
|
+
},
|
|
2592
|
+
};
|
|
2593
|
+
})(workflow, runExecutionData, connectionInputData, inputData, node, itemIndex) as IExecuteSingleFunctions;
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2596
|
+
export function getCredentialTestFunctions(): ICredentialTestFunctions {
|
|
2597
|
+
return {
|
|
2598
|
+
helpers: {
|
|
2599
|
+
request: proxyRequestToAxios,
|
|
2600
|
+
},
|
|
2601
|
+
};
|
|
2602
|
+
}
|
|
2603
|
+
|
|
2604
|
+
/**
|
|
2605
|
+
* Returns the execute functions regular nodes have access to in load-options-function.
|
|
2606
|
+
*
|
|
2607
|
+
* @export
|
|
2608
|
+
* @param {Workflow} workflow
|
|
2609
|
+
* @param {INode} node
|
|
2610
|
+
* @param {IWorkflowExecuteAdditionalData} additionalData
|
|
2611
|
+
* @returns {ILoadOptionsFunctions}
|
|
2612
|
+
*/
|
|
2613
|
+
export function getLoadOptionsFunctions(
|
|
2614
|
+
workflow: Workflow,
|
|
2615
|
+
node: INode,
|
|
2616
|
+
path: string,
|
|
2617
|
+
additionalData: IWorkflowExecuteAdditionalData,
|
|
2618
|
+
): ILoadOptionsFunctions {
|
|
2619
|
+
return ((workflow: Workflow, node: INode, path: string) => {
|
|
2620
|
+
const that = {
|
|
2621
|
+
async getCredentials(type: string): Promise<ICredentialDataDecryptedObject | undefined> {
|
|
2622
|
+
return getCredentials(workflow, node, type, additionalData, 'internal');
|
|
2623
|
+
},
|
|
2624
|
+
getCurrentNodeParameter: (
|
|
2625
|
+
parameterPath: string,
|
|
2626
|
+
):
|
|
2627
|
+
| NodeParameterValue
|
|
2628
|
+
| INodeParameters
|
|
2629
|
+
| NodeParameterValue[]
|
|
2630
|
+
| INodeParameters[]
|
|
2631
|
+
| object
|
|
2632
|
+
| undefined => {
|
|
2633
|
+
const nodeParameters = additionalData.currentNodeParameters;
|
|
2634
|
+
|
|
2635
|
+
if (parameterPath.charAt(0) === '&') {
|
|
2636
|
+
parameterPath = `${path.split('.').slice(1, -1).join('.')}.${parameterPath.slice(1)}`;
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
return get(nodeParameters, parameterPath);
|
|
2640
|
+
},
|
|
2641
|
+
getCurrentNodeParameters: (): INodeParameters | undefined => {
|
|
2642
|
+
return additionalData.currentNodeParameters;
|
|
2643
|
+
},
|
|
2644
|
+
getNode: () => {
|
|
2645
|
+
return getNode(node);
|
|
2646
|
+
},
|
|
2647
|
+
getNodeParameter: (
|
|
2648
|
+
parameterName: string,
|
|
2649
|
+
fallbackValue?: any,
|
|
2650
|
+
):
|
|
2651
|
+
| NodeParameterValue
|
|
2652
|
+
| INodeParameters
|
|
2653
|
+
| NodeParameterValue[]
|
|
2654
|
+
| INodeParameters[]
|
|
2655
|
+
| object => {
|
|
2656
|
+
const runExecutionData: IRunExecutionData | null = null;
|
|
2657
|
+
const itemIndex = 0;
|
|
2658
|
+
const runIndex = 0;
|
|
2659
|
+
const connectionInputData: INodeExecutionData[] = [];
|
|
2660
|
+
|
|
2661
|
+
return getNodeParameter(
|
|
2662
|
+
workflow,
|
|
2663
|
+
runExecutionData,
|
|
2664
|
+
runIndex,
|
|
2665
|
+
connectionInputData,
|
|
2666
|
+
node,
|
|
2667
|
+
parameterName,
|
|
2668
|
+
itemIndex,
|
|
2669
|
+
'internal' as WorkflowExecuteMode,
|
|
2670
|
+
getAdditionalKeys(additionalData),
|
|
2671
|
+
fallbackValue,
|
|
2672
|
+
);
|
|
2673
|
+
},
|
|
2674
|
+
getTimezone: (): string => {
|
|
2675
|
+
return getTimezone(workflow, additionalData);
|
|
2676
|
+
},
|
|
2677
|
+
getRestApiUrl: (): string => {
|
|
2678
|
+
return additionalData.restApiUrl;
|
|
2679
|
+
},
|
|
2680
|
+
helpers: {
|
|
2681
|
+
httpRequest,
|
|
2682
|
+
async requestWithAuthentication(
|
|
2683
|
+
this: IAllExecuteFunctions,
|
|
2684
|
+
credentialsType: string,
|
|
2685
|
+
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
|
2686
|
+
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
|
2687
|
+
): Promise<any> {
|
|
2688
|
+
return requestWithAuthentication.call(
|
|
2689
|
+
this,
|
|
2690
|
+
credentialsType,
|
|
2691
|
+
requestOptions,
|
|
2692
|
+
workflow,
|
|
2693
|
+
node,
|
|
2694
|
+
additionalData,
|
|
2695
|
+
additionalCredentialOptions,
|
|
2696
|
+
);
|
|
2697
|
+
},
|
|
2698
|
+
request: proxyRequestToAxios,
|
|
2699
|
+
async requestOAuth2(
|
|
2700
|
+
this: IAllExecuteFunctions,
|
|
2701
|
+
credentialsType: string,
|
|
2702
|
+
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
|
2703
|
+
oAuth2Options?: IOAuth2Options,
|
|
2704
|
+
): Promise<any> {
|
|
2705
|
+
return requestOAuth2.call(
|
|
2706
|
+
this,
|
|
2707
|
+
credentialsType,
|
|
2708
|
+
requestOptions,
|
|
2709
|
+
node,
|
|
2710
|
+
additionalData,
|
|
2711
|
+
oAuth2Options,
|
|
2712
|
+
);
|
|
2713
|
+
},
|
|
2714
|
+
async requestOAuth1(
|
|
2715
|
+
this: IAllExecuteFunctions,
|
|
2716
|
+
credentialsType: string,
|
|
2717
|
+
requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions,
|
|
2718
|
+
): Promise<any> {
|
|
2719
|
+
return requestOAuth1.call(this, credentialsType, requestOptions);
|
|
2720
|
+
},
|
|
2721
|
+
async httpRequestWithAuthentication(
|
|
2722
|
+
this: IAllExecuteFunctions,
|
|
2723
|
+
credentialsType: string,
|
|
2724
|
+
requestOptions: IHttpRequestOptions,
|
|
2725
|
+
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
|
2726
|
+
): Promise<any> {
|
|
2727
|
+
return httpRequestWithAuthentication.call(
|
|
2728
|
+
this,
|
|
2729
|
+
credentialsType,
|
|
2730
|
+
requestOptions,
|
|
2731
|
+
workflow,
|
|
2732
|
+
node,
|
|
2733
|
+
additionalData,
|
|
2734
|
+
additionalCredentialOptions,
|
|
2735
|
+
);
|
|
2736
|
+
},
|
|
2737
|
+
},
|
|
2738
|
+
getInstanceId: () => {
|
|
2739
|
+
// Return the node's instance ID if available, otherwise return an empty string
|
|
2740
|
+
return node.id || '';
|
|
2741
|
+
},
|
|
2742
|
+
};
|
|
2743
|
+
return that;
|
|
2744
|
+
})(workflow, node, path) as ILoadOptionsFunctions;
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2747
|
+
/**
|
|
2748
|
+
* Returns the execute functions regular nodes have access to in hook-function.
|
|
2749
|
+
*
|
|
2750
|
+
* @export
|
|
2751
|
+
* @param {Workflow} workflow
|
|
2752
|
+
* @param {INode} node
|
|
2753
|
+
* @param {IWorkflowExecuteAdditionalData} additionalData
|
|
2754
|
+
* @param {WorkflowExecuteMode} mode
|
|
2755
|
+
* @returns {IHookFunctions}
|
|
2756
|
+
*/
|
|
2757
|
+
export function getExecuteHookFunctions(
|
|
2758
|
+
workflow: Workflow,
|
|
2759
|
+
node: INode,
|
|
2760
|
+
additionalData: IWorkflowExecuteAdditionalData,
|
|
2761
|
+
mode: WorkflowExecuteMode,
|
|
2762
|
+
activation: WorkflowActivateMode,
|
|
2763
|
+
isTest?: boolean,
|
|
2764
|
+
webhookData?: IWebhookData,
|
|
2765
|
+
): IHookFunctions {
|
|
2766
|
+
return ((workflow: Workflow, node: INode) => {
|
|
2767
|
+
const that = {
|
|
2768
|
+
async getCredentials(type: string): Promise<ICredentialDataDecryptedObject | undefined> {
|
|
2769
|
+
return getCredentials(workflow, node, type, additionalData, mode);
|
|
2770
|
+
},
|
|
2771
|
+
getMode: (): WorkflowExecuteMode => {
|
|
2772
|
+
return mode;
|
|
2773
|
+
},
|
|
2774
|
+
getActivationMode: (): WorkflowActivateMode => {
|
|
2775
|
+
return activation;
|
|
2776
|
+
},
|
|
2777
|
+
getNode: () => {
|
|
2778
|
+
return getNode(node);
|
|
2779
|
+
},
|
|
2780
|
+
getNodeParameter: (
|
|
2781
|
+
parameterName: string,
|
|
2782
|
+
fallbackValue?: any,
|
|
2783
|
+
):
|
|
2784
|
+
| NodeParameterValue
|
|
2785
|
+
| INodeParameters
|
|
2786
|
+
| NodeParameterValue[]
|
|
2787
|
+
| INodeParameters[]
|
|
2788
|
+
| object => {
|
|
2789
|
+
const runExecutionData: IRunExecutionData | null = null;
|
|
2790
|
+
const itemIndex = 0;
|
|
2791
|
+
const runIndex = 0;
|
|
2792
|
+
const connectionInputData: INodeExecutionData[] = [];
|
|
2793
|
+
|
|
2794
|
+
return getNodeParameter(
|
|
2795
|
+
workflow,
|
|
2796
|
+
runExecutionData,
|
|
2797
|
+
runIndex,
|
|
2798
|
+
connectionInputData,
|
|
2799
|
+
node,
|
|
2800
|
+
parameterName,
|
|
2801
|
+
itemIndex,
|
|
2802
|
+
mode,
|
|
2803
|
+
getAdditionalKeys(additionalData),
|
|
2804
|
+
fallbackValue,
|
|
2805
|
+
);
|
|
2806
|
+
},
|
|
2807
|
+
getNodeWebhookUrl: (name: string): string | undefined => {
|
|
2808
|
+
return getNodeWebhookUrl(
|
|
2809
|
+
name,
|
|
2810
|
+
workflow,
|
|
2811
|
+
node,
|
|
2812
|
+
additionalData,
|
|
2813
|
+
mode,
|
|
2814
|
+
getAdditionalKeys(additionalData),
|
|
2815
|
+
isTest,
|
|
2816
|
+
);
|
|
2817
|
+
},
|
|
2818
|
+
getTimezone: (): string => {
|
|
2819
|
+
return getTimezone(workflow, additionalData);
|
|
2820
|
+
},
|
|
2821
|
+
getWebhookName(): string {
|
|
2822
|
+
if (webhookData === undefined) {
|
|
2823
|
+
throw new Error('Is only supported in webhook functions!');
|
|
2824
|
+
}
|
|
2825
|
+
return webhookData.webhookDescription.name;
|
|
2826
|
+
},
|
|
2827
|
+
getWebhookDescription(name: string): IWebhookDescription | undefined {
|
|
2828
|
+
return getWebhookDescription(name, workflow, node);
|
|
2829
|
+
},
|
|
2830
|
+
getWorkflow: () => {
|
|
2831
|
+
return getWorkflowMetadata(workflow);
|
|
2832
|
+
},
|
|
2833
|
+
getWorkflowStaticData(type: string): IDataObject {
|
|
2834
|
+
return workflow.getStaticData(type, node);
|
|
2835
|
+
},
|
|
2836
|
+
helpers: {
|
|
2837
|
+
httpRequest,
|
|
2838
|
+
async requestWithAuthentication(
|
|
2839
|
+
this: IAllExecuteFunctions,
|
|
2840
|
+
credentialsType: string,
|
|
2841
|
+
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
|
2842
|
+
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
|
2843
|
+
): Promise<any> {
|
|
2844
|
+
return requestWithAuthentication.call(
|
|
2845
|
+
this,
|
|
2846
|
+
credentialsType,
|
|
2847
|
+
requestOptions,
|
|
2848
|
+
workflow,
|
|
2849
|
+
node,
|
|
2850
|
+
additionalData,
|
|
2851
|
+
additionalCredentialOptions,
|
|
2852
|
+
);
|
|
2853
|
+
},
|
|
2854
|
+
request: proxyRequestToAxios,
|
|
2855
|
+
async requestOAuth2(
|
|
2856
|
+
this: IAllExecuteFunctions,
|
|
2857
|
+
credentialsType: string,
|
|
2858
|
+
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
|
2859
|
+
oAuth2Options?: IOAuth2Options,
|
|
2860
|
+
): Promise<any> {
|
|
2861
|
+
return requestOAuth2.call(
|
|
2862
|
+
this,
|
|
2863
|
+
credentialsType,
|
|
2864
|
+
requestOptions,
|
|
2865
|
+
node,
|
|
2866
|
+
additionalData,
|
|
2867
|
+
oAuth2Options,
|
|
2868
|
+
);
|
|
2869
|
+
},
|
|
2870
|
+
async requestOAuth1(
|
|
2871
|
+
this: IAllExecuteFunctions,
|
|
2872
|
+
credentialsType: string,
|
|
2873
|
+
requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions,
|
|
2874
|
+
): Promise<any> {
|
|
2875
|
+
return requestOAuth1.call(this, credentialsType, requestOptions);
|
|
2876
|
+
},
|
|
2877
|
+
async httpRequestWithAuthentication(
|
|
2878
|
+
this: IAllExecuteFunctions,
|
|
2879
|
+
credentialsType: string,
|
|
2880
|
+
requestOptions: IHttpRequestOptions,
|
|
2881
|
+
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
|
2882
|
+
): Promise<any> {
|
|
2883
|
+
return httpRequestWithAuthentication.call(
|
|
2884
|
+
this,
|
|
2885
|
+
credentialsType,
|
|
2886
|
+
requestOptions,
|
|
2887
|
+
workflow,
|
|
2888
|
+
node,
|
|
2889
|
+
additionalData,
|
|
2890
|
+
additionalCredentialOptions,
|
|
2891
|
+
);
|
|
2892
|
+
},
|
|
2893
|
+
},
|
|
2894
|
+
getInstanceId: () => {
|
|
2895
|
+
// Return the node's instance ID if available, otherwise return an empty string
|
|
2896
|
+
return node.id || '';
|
|
2897
|
+
},
|
|
2898
|
+
};
|
|
2899
|
+
return that;
|
|
2900
|
+
})(workflow, node) as IHookFunctions;
|
|
2901
|
+
}
|
|
2902
|
+
|
|
2903
|
+
/**
|
|
2904
|
+
* Returns the execute functions regular nodes have access to when webhook-function is defined.
|
|
2905
|
+
*
|
|
2906
|
+
* @export
|
|
2907
|
+
* @param {Workflow} workflow
|
|
2908
|
+
* @param {IRunExecutionData} runExecutionData
|
|
2909
|
+
* @param {INode} node
|
|
2910
|
+
* @param {IWorkflowExecuteAdditionalData} additionalData
|
|
2911
|
+
* @param {WorkflowExecuteMode} mode
|
|
2912
|
+
* @returns {IWebhookFunctions}
|
|
2913
|
+
*/
|
|
2914
|
+
export function getExecuteWebhookFunctions(
|
|
2915
|
+
workflow: Workflow,
|
|
2916
|
+
node: INode,
|
|
2917
|
+
additionalData: IWorkflowExecuteAdditionalData,
|
|
2918
|
+
mode: WorkflowExecuteMode,
|
|
2919
|
+
webhookData: IWebhookData,
|
|
2920
|
+
runExecutionData: IRunExecutionData | undefined,
|
|
2921
|
+
): IWebhookFunctions {
|
|
2922
|
+
return ((workflow: Workflow, node: INode) => {
|
|
2923
|
+
return {
|
|
2924
|
+
logger: Logger,
|
|
2925
|
+
evaluateExpression: (expression: string, itemIndex?: number) => {
|
|
2926
|
+
//TODO: Implement this EVALUATE EXPRESSION in get webhook execute function
|
|
2927
|
+
return {} as NodeParameterValueType;
|
|
2928
|
+
},
|
|
2929
|
+
getChildNodes: (nodeName: string, options?: { includeNodeParameters?: boolean }) => {
|
|
2930
|
+
return [];
|
|
2931
|
+
},
|
|
2932
|
+
getParentNodes: (nodeName: string): NodeTypeAndVersion[] => {
|
|
2933
|
+
return [];
|
|
2934
|
+
},
|
|
2935
|
+
getBodyData(): IDataObject {
|
|
2936
|
+
if (additionalData.httpRequest === undefined) {
|
|
2937
|
+
throw new Error('Request is missing!');
|
|
2938
|
+
}
|
|
2939
|
+
return additionalData.httpRequest.body;
|
|
2940
|
+
},
|
|
2941
|
+
async getCredentials(type: string): Promise<ICredentialDataDecryptedObject | undefined> {
|
|
2942
|
+
return getCredentials(workflow, node, type, additionalData, mode);
|
|
2943
|
+
},
|
|
2944
|
+
nodeHelpers: {
|
|
2945
|
+
copyBinaryFile: async (filePath: string, fileName: string, mimeType?: string): Promise<IBinaryData> => {
|
|
2946
|
+
// Use fs.promises.readFile instead of fsReadFileAsync
|
|
2947
|
+
const buffer = await fs.readFile(filePath);
|
|
2948
|
+
return prepareBinaryData(buffer, workflow.id || '', filePath, mimeType);
|
|
2949
|
+
},
|
|
2950
|
+
},
|
|
2951
|
+
getHeaderData(): object {
|
|
2952
|
+
if (additionalData.httpRequest === undefined) {
|
|
2953
|
+
throw new Error('Request is missing!');
|
|
2954
|
+
}
|
|
2955
|
+
return additionalData.httpRequest.headers;
|
|
2956
|
+
},
|
|
2957
|
+
getMode: (): WorkflowExecuteMode => {
|
|
2958
|
+
return mode;
|
|
2959
|
+
},
|
|
2960
|
+
getNode: () => {
|
|
2961
|
+
return getNode(node);
|
|
2962
|
+
},
|
|
2963
|
+
getNodeParameter: (
|
|
2964
|
+
parameterName: string,
|
|
2965
|
+
fallbackValue?: any,
|
|
2966
|
+
options?: IGetNodeParameterOptions,
|
|
2967
|
+
):
|
|
2968
|
+
| NodeParameterValue
|
|
2969
|
+
| INodeParameters
|
|
2970
|
+
| NodeParameterValue[]
|
|
2971
|
+
| INodeParameters[]
|
|
2972
|
+
| object => {
|
|
2973
|
+
const itemIndex = 0;
|
|
2974
|
+
const runIndex = 0;
|
|
2975
|
+
|
|
2976
|
+
let connectionInputData: INodeExecutionData[] = [];
|
|
2977
|
+
let executionData: IExecuteData | undefined;
|
|
2978
|
+
if (runExecutionData?.executionData !== undefined) {
|
|
2979
|
+
executionData = runExecutionData.executionData.nodeExecutionStack[0];
|
|
2980
|
+
if (executionData !== undefined) {
|
|
2981
|
+
connectionInputData = executionData.data['main']?.[0] || [];
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
|
|
2985
|
+
return getNodeParameter(
|
|
2986
|
+
workflow,
|
|
2987
|
+
runExecutionData || null,
|
|
2988
|
+
runIndex,
|
|
2989
|
+
connectionInputData,
|
|
2990
|
+
node,
|
|
2991
|
+
parameterName,
|
|
2992
|
+
itemIndex,
|
|
2993
|
+
mode,
|
|
2994
|
+
getAdditionalKeys(additionalData),
|
|
2995
|
+
fallbackValue,
|
|
2996
|
+
);
|
|
2997
|
+
},
|
|
2998
|
+
getParamsData(): object {
|
|
2999
|
+
if (additionalData.httpRequest === undefined) {
|
|
3000
|
+
throw new Error('Request is missing!');
|
|
3001
|
+
}
|
|
3002
|
+
return additionalData.httpRequest.params;
|
|
3003
|
+
},
|
|
3004
|
+
getQueryData(): object {
|
|
3005
|
+
if (additionalData.httpRequest === undefined) {
|
|
3006
|
+
throw new Error('Request is missing!');
|
|
3007
|
+
}
|
|
3008
|
+
return additionalData.httpRequest.query;
|
|
3009
|
+
},
|
|
3010
|
+
getRequestObject(): Request {
|
|
3011
|
+
if (additionalData.httpRequest === undefined) {
|
|
3012
|
+
throw new Error('Request is missing!');
|
|
3013
|
+
}
|
|
3014
|
+
return additionalData.httpRequest;
|
|
3015
|
+
},
|
|
3016
|
+
getResponseObject(): Response {
|
|
3017
|
+
if (additionalData.httpResponse === undefined) {
|
|
3018
|
+
throw new Error('Response is missing!');
|
|
3019
|
+
}
|
|
3020
|
+
return additionalData.httpResponse;
|
|
3021
|
+
},
|
|
3022
|
+
getNodeWebhookUrl: (name: string): string | undefined => {
|
|
3023
|
+
return getNodeWebhookUrl(
|
|
3024
|
+
name,
|
|
3025
|
+
workflow,
|
|
3026
|
+
node,
|
|
3027
|
+
additionalData,
|
|
3028
|
+
mode,
|
|
3029
|
+
getAdditionalKeys(additionalData),
|
|
3030
|
+
);
|
|
3031
|
+
},
|
|
3032
|
+
getTimezone: (): string => {
|
|
3033
|
+
return getTimezone(workflow, additionalData);
|
|
3034
|
+
},
|
|
3035
|
+
getWorkflow: () => {
|
|
3036
|
+
return getWorkflowMetadata(workflow);
|
|
3037
|
+
},
|
|
3038
|
+
getWorkflowStaticData(type: string): IDataObject {
|
|
3039
|
+
return workflow.getStaticData(type, node);
|
|
3040
|
+
},
|
|
3041
|
+
getWebhookName(): string {
|
|
3042
|
+
return webhookData.webhookDescription.name;
|
|
3043
|
+
},
|
|
3044
|
+
addInputData(
|
|
3045
|
+
data: INodeExecutionData[][] | ExecutionError,
|
|
3046
|
+
node: INode
|
|
3047
|
+
): { index: number, inputExecutionData: IRunExecutionData } {
|
|
3048
|
+
const nodeName = node.name;
|
|
3049
|
+
let currentNodeRunIndex = 0;
|
|
3050
|
+
runExecutionData = {
|
|
3051
|
+
startData: {
|
|
3052
|
+
destinationNode: nodeName,
|
|
3053
|
+
},
|
|
3054
|
+
resultData: {
|
|
3055
|
+
error: undefined,
|
|
3056
|
+
runData: {
|
|
3057
|
+
[nodeName]: [JSON.parse(JSON.stringify(data))]
|
|
3058
|
+
},
|
|
3059
|
+
lastNodeExecuted: nodeName,
|
|
3060
|
+
},
|
|
3061
|
+
executionData: {
|
|
3062
|
+
contextData: {},
|
|
3063
|
+
nodeExecutionStack: [],
|
|
3064
|
+
waitingExecution: {},
|
|
3065
|
+
},
|
|
3066
|
+
waitTill: undefined,
|
|
3067
|
+
}
|
|
3068
|
+
|
|
3069
|
+
return { index: currentNodeRunIndex, inputExecutionData: runExecutionData };
|
|
3070
|
+
},
|
|
3071
|
+
addOutputData(
|
|
3072
|
+
data: INodeExecutionData[][] | ExecutionError,
|
|
3073
|
+
node: INode,
|
|
3074
|
+
): { outputExecutionData: IRunExecutionData } {
|
|
3075
|
+
const nodeName = node.name;
|
|
3076
|
+
const outputExecutionData = {
|
|
3077
|
+
startData: {
|
|
3078
|
+
destinationNode: nodeName,
|
|
3079
|
+
},
|
|
3080
|
+
resultData: {
|
|
3081
|
+
error: undefined,
|
|
3082
|
+
runData: {
|
|
3083
|
+
[nodeName]: [JSON.parse(JSON.stringify(data))]
|
|
3084
|
+
},
|
|
3085
|
+
lastNodeExecuted: nodeName,
|
|
3086
|
+
},
|
|
3087
|
+
executionData: {
|
|
3088
|
+
contextData: {},
|
|
3089
|
+
nodeExecutionStack: [],
|
|
3090
|
+
waitingExecution: {},
|
|
3091
|
+
},
|
|
3092
|
+
waitTill: undefined,
|
|
3093
|
+
}
|
|
3094
|
+
return { outputExecutionData };
|
|
3095
|
+
|
|
3096
|
+
},
|
|
3097
|
+
logAiEvent: (event: AiEvent, data?: string): void => {
|
|
3098
|
+
// Simple implementation for logAiEvent in webhook functions
|
|
3099
|
+
return;
|
|
3100
|
+
},
|
|
3101
|
+
prepareOutputData: NodeHelpers.prepareOutputData,
|
|
3102
|
+
helpers: {
|
|
3103
|
+
httpRequest,
|
|
3104
|
+
async requestWithAuthentication(
|
|
3105
|
+
this: IAllExecuteFunctions,
|
|
3106
|
+
credentialsType: string,
|
|
3107
|
+
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
|
3108
|
+
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
|
3109
|
+
): Promise<any> {
|
|
3110
|
+
return requestWithAuthentication.call(
|
|
3111
|
+
this,
|
|
3112
|
+
credentialsType,
|
|
3113
|
+
requestOptions,
|
|
3114
|
+
workflow,
|
|
3115
|
+
node,
|
|
3116
|
+
additionalData,
|
|
3117
|
+
additionalCredentialOptions,
|
|
3118
|
+
);
|
|
3119
|
+
},
|
|
3120
|
+
async prepareBinaryData(
|
|
3121
|
+
binaryData: Buffer,
|
|
3122
|
+
filePath?: string,
|
|
3123
|
+
mimeType?: string,
|
|
3124
|
+
): Promise<IBinaryData> {
|
|
3125
|
+
return prepareBinaryData.call(
|
|
3126
|
+
this,
|
|
3127
|
+
binaryData,
|
|
3128
|
+
additionalData.executionId!,
|
|
3129
|
+
filePath,
|
|
3130
|
+
mimeType,
|
|
3131
|
+
);
|
|
3132
|
+
},
|
|
3133
|
+
request: proxyRequestToAxios,
|
|
3134
|
+
async requestOAuth2(
|
|
3135
|
+
this: IAllExecuteFunctions,
|
|
3136
|
+
credentialsType: string,
|
|
3137
|
+
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
|
3138
|
+
oAuth2Options?: IOAuth2Options,
|
|
3139
|
+
): Promise<any> {
|
|
3140
|
+
return requestOAuth2.call(
|
|
3141
|
+
this,
|
|
3142
|
+
credentialsType,
|
|
3143
|
+
requestOptions,
|
|
3144
|
+
node,
|
|
3145
|
+
additionalData,
|
|
3146
|
+
oAuth2Options,
|
|
3147
|
+
);
|
|
3148
|
+
},
|
|
3149
|
+
async requestOAuth1(
|
|
3150
|
+
this: IAllExecuteFunctions,
|
|
3151
|
+
credentialsType: string,
|
|
3152
|
+
requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions,
|
|
3153
|
+
): Promise<any> {
|
|
3154
|
+
return requestOAuth1.call(this, credentialsType, requestOptions);
|
|
3155
|
+
},
|
|
3156
|
+
async httpRequestWithAuthentication(
|
|
3157
|
+
this: IAllExecuteFunctions,
|
|
3158
|
+
credentialsType: string,
|
|
3159
|
+
requestOptions: IHttpRequestOptions,
|
|
3160
|
+
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
|
3161
|
+
): Promise<any> {
|
|
3162
|
+
return httpRequestWithAuthentication.call(
|
|
3163
|
+
this,
|
|
3164
|
+
credentialsType,
|
|
3165
|
+
requestOptions,
|
|
3166
|
+
workflow,
|
|
3167
|
+
node,
|
|
3168
|
+
additionalData,
|
|
3169
|
+
additionalCredentialOptions,
|
|
3170
|
+
);
|
|
3171
|
+
},
|
|
3172
|
+
returnJsonArray,
|
|
3173
|
+
},
|
|
3174
|
+
getInstanceId: () => {
|
|
3175
|
+
// Return the node's instance ID if available, otherwise return an empty string
|
|
3176
|
+
return node.id || '';
|
|
3177
|
+
},
|
|
3178
|
+
};
|
|
3179
|
+
})(workflow, node) as IWebhookFunctions;
|
|
3180
|
+
}
|
|
3181
|
+
|
|
3182
|
+
export function getSupplyDataFunctions(
|
|
3183
|
+
workflow: Workflow,
|
|
3184
|
+
runExecutionData: IRunExecutionData,
|
|
3185
|
+
runIndex: number,
|
|
3186
|
+
connectionInputData: INodeExecutionData[],
|
|
3187
|
+
inputData: ITaskDataConnections,
|
|
3188
|
+
node: INode,
|
|
3189
|
+
additionalData: IWorkflowExecuteAdditionalData,
|
|
3190
|
+
mode: WorkflowExecuteMode,
|
|
3191
|
+
nodeTypeData: INodeType,
|
|
3192
|
+
closeFunctions: CloseFunction[],
|
|
3193
|
+
): ISupplyDataFunctions {
|
|
3194
|
+
return ((workflow, runExecutionData, connectionInputData, inputData, node, nodeTypeData, closeFunctions) => {
|
|
3195
|
+
return {
|
|
3196
|
+
logger: Logger,
|
|
3197
|
+
continueOnFail: () => {
|
|
3198
|
+
return continueOnFail(node);
|
|
3199
|
+
},
|
|
3200
|
+
evaluateExpression: (expression: string, itemIndex: number) => {
|
|
3201
|
+
return workflow.expression.resolveSimpleParameterValue(
|
|
3202
|
+
`=${expression}`,
|
|
3203
|
+
{},
|
|
3204
|
+
runExecutionData,
|
|
3205
|
+
runIndex,
|
|
3206
|
+
itemIndex,
|
|
3207
|
+
node.name,
|
|
3208
|
+
connectionInputData,
|
|
3209
|
+
mode,
|
|
3210
|
+
getAdditionalKeys(additionalData),
|
|
3211
|
+
);
|
|
3212
|
+
},
|
|
3213
|
+
async executeWorkflow(
|
|
3214
|
+
workflowInfo: IExecuteWorkflowInfo,
|
|
3215
|
+
inputData?: INodeExecutionData[],
|
|
3216
|
+
): Promise<any> {
|
|
3217
|
+
return additionalData
|
|
3218
|
+
.executeWorkflow(workflowInfo, additionalData, inputData)
|
|
3219
|
+
.then(async (result) =>
|
|
3220
|
+
BinaryDataManager.getInstance().duplicateBinaryData(
|
|
3221
|
+
result,
|
|
3222
|
+
additionalData.executionId!,
|
|
3223
|
+
),
|
|
3224
|
+
);
|
|
3225
|
+
},
|
|
3226
|
+
getContext(type: string): IContextObject {
|
|
3227
|
+
return NodeHelpers.getContext(runExecutionData, type, node);
|
|
3228
|
+
},
|
|
3229
|
+
async getCredentials(
|
|
3230
|
+
type: string,
|
|
3231
|
+
itemIndex?: number,
|
|
3232
|
+
): Promise<ICredentialDataDecryptedObject | undefined> {
|
|
3233
|
+
return getCredentials(
|
|
3234
|
+
workflow,
|
|
3235
|
+
node,
|
|
3236
|
+
type,
|
|
3237
|
+
additionalData,
|
|
3238
|
+
mode,
|
|
3239
|
+
runExecutionData,
|
|
3240
|
+
runIndex,
|
|
3241
|
+
connectionInputData,
|
|
3242
|
+
itemIndex,
|
|
3243
|
+
);
|
|
3244
|
+
},
|
|
3245
|
+
getExecutionId: (): string => {
|
|
3246
|
+
return additionalData.executionId!;
|
|
3247
|
+
},
|
|
3248
|
+
getInputData: (inputIndex = 0, inputName = 'main') => {
|
|
3249
|
+
if (!Object.prototype.hasOwnProperty.call(inputData, inputName)) {
|
|
3250
|
+
// Return empty array because else it would throw error when nothing is connected to input
|
|
3251
|
+
return [];
|
|
3252
|
+
}
|
|
3253
|
+
|
|
3254
|
+
// TODO: Check if nodeType has input with that index defined
|
|
3255
|
+
if (inputData[inputName].length < inputIndex) {
|
|
3256
|
+
throw new Error(`Could not get input index "${inputIndex}" of input "${inputName}"!`);
|
|
3257
|
+
}
|
|
3258
|
+
|
|
3259
|
+
if (inputData[inputName][inputIndex] === null) {
|
|
3260
|
+
// return [];
|
|
3261
|
+
throw new Error(`Value "${inputIndex}" of input "${inputName}" did not get set!`);
|
|
3262
|
+
}
|
|
3263
|
+
|
|
3264
|
+
return inputData[inputName][inputIndex] as INodeExecutionData[];
|
|
3265
|
+
},
|
|
3266
|
+
getNodeParameter: (
|
|
3267
|
+
parameterName: string,
|
|
3268
|
+
itemIndex: number,
|
|
3269
|
+
fallbackValue?: any,
|
|
3270
|
+
):
|
|
3271
|
+
| NodeParameterValue
|
|
3272
|
+
| INodeParameters
|
|
3273
|
+
| NodeParameterValue[]
|
|
3274
|
+
| INodeParameters[]
|
|
3275
|
+
| object => {
|
|
3276
|
+
return getNodeParameter(
|
|
3277
|
+
workflow,
|
|
3278
|
+
runExecutionData,
|
|
3279
|
+
runIndex,
|
|
3280
|
+
connectionInputData,
|
|
3281
|
+
node,
|
|
3282
|
+
parameterName,
|
|
3283
|
+
itemIndex,
|
|
3284
|
+
mode,
|
|
3285
|
+
getAdditionalKeys(additionalData),
|
|
3286
|
+
fallbackValue,
|
|
3287
|
+
);
|
|
3288
|
+
},
|
|
3289
|
+
getMode: (): WorkflowExecuteMode => {
|
|
3290
|
+
return mode;
|
|
3291
|
+
},
|
|
3292
|
+
getNode: () => {
|
|
3293
|
+
return getNode(node);
|
|
3294
|
+
},
|
|
3295
|
+
getRestApiUrl: (): string => {
|
|
3296
|
+
return additionalData.restApiUrl;
|
|
3297
|
+
},
|
|
3298
|
+
getTimezone: (): string => {
|
|
3299
|
+
return getTimezone(workflow, additionalData);
|
|
3300
|
+
},
|
|
3301
|
+
getWorkflow: () => {
|
|
3302
|
+
return getWorkflowMetadata(workflow);
|
|
3303
|
+
},
|
|
3304
|
+
getWorkflowDataProxy: (itemIndex: number): IWorkflowDataProxyData => {
|
|
3305
|
+
const dataProxy = new WorkflowDataProxy(
|
|
3306
|
+
workflow,
|
|
3307
|
+
runExecutionData,
|
|
3308
|
+
runIndex,
|
|
3309
|
+
itemIndex,
|
|
3310
|
+
node.name,
|
|
3311
|
+
connectionInputData,
|
|
3312
|
+
{},
|
|
3313
|
+
mode,
|
|
3314
|
+
getAdditionalKeys(additionalData),
|
|
3315
|
+
);
|
|
3316
|
+
return dataProxy.getDataProxy();
|
|
3317
|
+
},
|
|
3318
|
+
getWorkflowStaticData(type: string): IDataObject {
|
|
3319
|
+
return workflow.getStaticData(type, node);
|
|
3320
|
+
},
|
|
3321
|
+
prepareOutputData: NodeHelpers.prepareOutputData,
|
|
3322
|
+
async putExecutionToWait(waitTill: Date): Promise<void> {
|
|
3323
|
+
runExecutionData.waitTill = waitTill;
|
|
3324
|
+
},
|
|
3325
|
+
sendMessageToUI(...args: any[]): void {
|
|
3326
|
+
if (mode !== 'manual') {
|
|
3327
|
+
return;
|
|
3328
|
+
}
|
|
3329
|
+
try {
|
|
3330
|
+
if (additionalData.sendMessageToUI) {
|
|
3331
|
+
additionalData.sendMessageToUI(node.name, args);
|
|
3332
|
+
}
|
|
3333
|
+
} catch (error: any) {
|
|
3334
|
+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
3335
|
+
Logger.warn(`There was a problem sending messsage to UI: ${error.message}`);
|
|
3336
|
+
}
|
|
3337
|
+
},
|
|
3338
|
+
async sendResponse(response: IExecuteResponsePromiseData): Promise<void> {
|
|
3339
|
+
await additionalData.hooks?.executeHookFunctions('sendResponse', [response]);
|
|
3340
|
+
},
|
|
3341
|
+
helpers: {
|
|
3342
|
+
httpRequest,
|
|
3343
|
+
async requestWithAuthentication(
|
|
3344
|
+
this: IAllExecuteFunctions,
|
|
3345
|
+
credentialsType: string,
|
|
3346
|
+
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
|
3347
|
+
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
|
3348
|
+
): Promise<any> {
|
|
3349
|
+
return requestWithAuthentication.call(
|
|
3350
|
+
this,
|
|
3351
|
+
credentialsType,
|
|
3352
|
+
requestOptions,
|
|
3353
|
+
workflow,
|
|
3354
|
+
node,
|
|
3355
|
+
additionalData,
|
|
3356
|
+
additionalCredentialOptions,
|
|
3357
|
+
);
|
|
3358
|
+
},
|
|
3359
|
+
assertBinaryData(
|
|
3360
|
+
itemIndex: number,
|
|
3361
|
+
propertyName: string,
|
|
3362
|
+
inputIndex = 0,
|
|
3363
|
+
): IBinaryData {
|
|
3364
|
+
const binaryKeyData = inputData['main'][inputIndex]![itemIndex].binary;
|
|
3365
|
+
if (binaryKeyData === undefined) {
|
|
3366
|
+
throw new NodeOperationError(
|
|
3367
|
+
node,
|
|
3368
|
+
`This operation expects the node's input data to contain a binary file '${propertyName}', but none was found [item ${itemIndex}]`,
|
|
3369
|
+
{
|
|
3370
|
+
itemIndex,
|
|
3371
|
+
description: 'Make sure that the previous node outputs a binary file',
|
|
3372
|
+
},
|
|
3373
|
+
);
|
|
3374
|
+
}
|
|
3375
|
+
|
|
3376
|
+
const binaryPropertyData = binaryKeyData[propertyName];
|
|
3377
|
+
if (binaryPropertyData === undefined) {
|
|
3378
|
+
throw new NodeOperationError(
|
|
3379
|
+
node,
|
|
3380
|
+
`The item has no binary field '${propertyName}' [item ${itemIndex}]`,
|
|
3381
|
+
{
|
|
3382
|
+
itemIndex,
|
|
3383
|
+
description:
|
|
3384
|
+
'Check that the parameter where you specified the input binary field name is correct, and that it matches a field in the binary input',
|
|
3385
|
+
},
|
|
3386
|
+
);
|
|
3387
|
+
}
|
|
3388
|
+
|
|
3389
|
+
return binaryPropertyData;
|
|
3390
|
+
},
|
|
3391
|
+
|
|
3392
|
+
async prepareBinaryData(
|
|
3393
|
+
binaryData: Buffer,
|
|
3394
|
+
filePath?: string,
|
|
3395
|
+
mimeType?: string,
|
|
3396
|
+
): Promise<IBinaryData> {
|
|
3397
|
+
return prepareBinaryData.call(
|
|
3398
|
+
this,
|
|
3399
|
+
binaryData,
|
|
3400
|
+
additionalData.executionId!,
|
|
3401
|
+
filePath,
|
|
3402
|
+
mimeType,
|
|
3403
|
+
);
|
|
3404
|
+
},
|
|
3405
|
+
async getBinaryDataBuffer(
|
|
3406
|
+
itemIndex: number,
|
|
3407
|
+
propertyName: string,
|
|
3408
|
+
inputIndex = 0,
|
|
3409
|
+
): Promise<Buffer> {
|
|
3410
|
+
return getBinaryDataBuffer.call(this, inputData, itemIndex, propertyName, inputIndex);
|
|
3411
|
+
},
|
|
3412
|
+
request: proxyRequestToAxios,
|
|
3413
|
+
async requestOAuth2(
|
|
3414
|
+
this: IAllExecuteFunctions,
|
|
3415
|
+
credentialsType: string,
|
|
3416
|
+
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
|
3417
|
+
oAuth2Options?: IOAuth2Options,
|
|
3418
|
+
): Promise<any> {
|
|
3419
|
+
return requestOAuth2.call(
|
|
3420
|
+
this,
|
|
3421
|
+
credentialsType,
|
|
3422
|
+
requestOptions,
|
|
3423
|
+
node,
|
|
3424
|
+
additionalData,
|
|
3425
|
+
oAuth2Options,
|
|
3426
|
+
);
|
|
3427
|
+
},
|
|
3428
|
+
async requestOAuth1(
|
|
3429
|
+
this: IAllExecuteFunctions,
|
|
3430
|
+
credentialsType: string,
|
|
3431
|
+
requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions,
|
|
3432
|
+
): Promise<any> {
|
|
3433
|
+
return requestOAuth1.call(this, credentialsType, requestOptions);
|
|
3434
|
+
},
|
|
3435
|
+
async httpRequestWithAuthentication(
|
|
3436
|
+
this: IAllExecuteFunctions,
|
|
3437
|
+
credentialsType: string,
|
|
3438
|
+
requestOptions: IHttpRequestOptions,
|
|
3439
|
+
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
|
3440
|
+
): Promise<any> {
|
|
3441
|
+
return httpRequestWithAuthentication.call(
|
|
3442
|
+
this,
|
|
3443
|
+
credentialsType,
|
|
3444
|
+
requestOptions,
|
|
3445
|
+
workflow,
|
|
3446
|
+
node,
|
|
3447
|
+
additionalData,
|
|
3448
|
+
additionalCredentialOptions,
|
|
3449
|
+
);
|
|
3450
|
+
},
|
|
3451
|
+
returnJsonArray,
|
|
3452
|
+
normalizeItems,
|
|
3453
|
+
},
|
|
3454
|
+
getInstanceId: () => {
|
|
3455
|
+
// Return the node's instance ID if available, otherwise return an empty string
|
|
3456
|
+
return node.id || '';
|
|
3457
|
+
},
|
|
3458
|
+
|
|
3459
|
+
addInputData(
|
|
3460
|
+
data: INodeExecutionData[][] | ExecutionError,
|
|
3461
|
+
node: INode
|
|
3462
|
+
): { index: number, inputExecutionData: IRunExecutionData } {
|
|
3463
|
+
const nodeName = node.name;
|
|
3464
|
+
let currentNodeRunIndex = 0;
|
|
3465
|
+
runExecutionData = {
|
|
3466
|
+
startData: {
|
|
3467
|
+
destinationNode: nodeName,
|
|
3468
|
+
},
|
|
3469
|
+
resultData: {
|
|
3470
|
+
error: undefined,
|
|
3471
|
+
runData: {
|
|
3472
|
+
[nodeName]: [JSON.parse(JSON.stringify(data))]
|
|
3473
|
+
},
|
|
3474
|
+
lastNodeExecuted: nodeName,
|
|
3475
|
+
},
|
|
3476
|
+
executionData: {
|
|
3477
|
+
contextData: {},
|
|
3478
|
+
nodeExecutionStack: [],
|
|
3479
|
+
waitingExecution: {},
|
|
3480
|
+
},
|
|
3481
|
+
waitTill: undefined,
|
|
3482
|
+
}
|
|
3483
|
+
|
|
3484
|
+
return { index: currentNodeRunIndex, inputExecutionData: runExecutionData };
|
|
3485
|
+
},
|
|
3486
|
+
addOutputData(
|
|
3487
|
+
data: INodeExecutionData[][] | ExecutionError,
|
|
3488
|
+
node: INode,
|
|
3489
|
+
): { outputExecutionData: IRunExecutionData } {
|
|
3490
|
+
const nodeName = node.name;
|
|
3491
|
+
const outputExecutionData = {
|
|
3492
|
+
startData: {
|
|
3493
|
+
destinationNode: nodeName,
|
|
3494
|
+
},
|
|
3495
|
+
resultData: {
|
|
3496
|
+
error: undefined,
|
|
3497
|
+
runData: {
|
|
3498
|
+
[nodeName]: [JSON.parse(JSON.stringify(data))]
|
|
3499
|
+
},
|
|
3500
|
+
lastNodeExecuted: nodeName,
|
|
3501
|
+
},
|
|
3502
|
+
executionData: {
|
|
3503
|
+
contextData: {},
|
|
3504
|
+
nodeExecutionStack: [],
|
|
3505
|
+
waitingExecution: {},
|
|
3506
|
+
},
|
|
3507
|
+
waitTill: undefined,
|
|
3508
|
+
}
|
|
3509
|
+
return { outputExecutionData };
|
|
3510
|
+
|
|
3511
|
+
},
|
|
3512
|
+
async getInputConnectionData(
|
|
3513
|
+
this: IExecuteFunctions | ISupplyDataFunctions,
|
|
3514
|
+
connectionType: NodeConnectionType,
|
|
3515
|
+
itemIndex: number,
|
|
3516
|
+
): Promise<unknown> {
|
|
3517
|
+
return await getInputConnectionData.call(
|
|
3518
|
+
this,
|
|
3519
|
+
workflow,
|
|
3520
|
+
runExecutionData,
|
|
3521
|
+
runIndex,
|
|
3522
|
+
connectionInputData,
|
|
3523
|
+
inputData,
|
|
3524
|
+
additionalData,
|
|
3525
|
+
connectionType,
|
|
3526
|
+
mode,
|
|
3527
|
+
itemIndex,
|
|
3528
|
+
nodeTypeData,
|
|
3529
|
+
closeFunctions,
|
|
3530
|
+
);
|
|
3531
|
+
},
|
|
3532
|
+
};
|
|
3533
|
+
})(workflow, runExecutionData, connectionInputData, inputData, node, nodeTypeData, closeFunctions);
|
|
3534
|
+
}
|
|
3535
|
+
|
|
3536
|
+
export async function getInputConnectionData(
|
|
3537
|
+
this: IExecuteFunctions | ISupplyDataFunctions,
|
|
3538
|
+
workflow: Workflow,
|
|
3539
|
+
runExecutionData: IRunExecutionData,
|
|
3540
|
+
runIndex: number,
|
|
3541
|
+
connectionInputData: INodeExecutionData[],
|
|
3542
|
+
inputData: ITaskDataConnections,
|
|
3543
|
+
additionalData: IWorkflowExecuteAdditionalData,
|
|
3544
|
+
connectionType: NodeConnectionType,
|
|
3545
|
+
mode: WorkflowExecuteMode,
|
|
3546
|
+
itemIndex: number,
|
|
3547
|
+
nodeTypeData: INodeType,
|
|
3548
|
+
closeFunctions: CloseFunction[],
|
|
3549
|
+
): Promise<unknown> {
|
|
3550
|
+
const parentNode = this.getNode();
|
|
3551
|
+
const startTime = Date.now();
|
|
3552
|
+
|
|
3553
|
+
const nodeInputs = getNodeInputs(workflow, parentNode, nodeTypeData.description).map(
|
|
3554
|
+
(input) => (typeof input === 'string' ? { type: input } : input),
|
|
3555
|
+
) as INodeInputConfiguration[];
|
|
3556
|
+
const inputConfiguration = nodeInputs.find((input) => input.type === connectionType);
|
|
3557
|
+
if (inputConfiguration === undefined) {
|
|
3558
|
+
throw new NodeOperationError(
|
|
3559
|
+
parentNode,
|
|
3560
|
+
`Node does not have input of type`,
|
|
3561
|
+
{
|
|
3562
|
+
description: `Node ${parentNode.name} does not have input of type ${connectionType}`,
|
|
3563
|
+
}
|
|
3564
|
+
);
|
|
3565
|
+
}
|
|
3566
|
+
|
|
3567
|
+
const connectedNodes = getConnectedNodes(workflow, parentNode, connectionType);
|
|
3568
|
+
if (connectedNodes.length === 0) {
|
|
3569
|
+
if (inputConfiguration.required) {
|
|
3570
|
+
throw new NodeOperationError(
|
|
3571
|
+
parentNode,
|
|
3572
|
+
`A ${inputConfiguration?.displayName ?? connectionType} sub-node must be connected and enabled`,
|
|
3573
|
+
);
|
|
3574
|
+
}
|
|
3575
|
+
return inputConfiguration.maxConnections === 1 ? undefined : [];
|
|
3576
|
+
}
|
|
3577
|
+
|
|
3578
|
+
if (
|
|
3579
|
+
inputConfiguration.maxConnections !== undefined &&
|
|
3580
|
+
connectedNodes.length > inputConfiguration.maxConnections
|
|
3581
|
+
) {
|
|
3582
|
+
throw new NodeOperationError(
|
|
3583
|
+
parentNode,
|
|
3584
|
+
`Only ${inputConfiguration.maxConnections} ${connectionType} sub-nodes are/is allowed to be connected`,
|
|
3585
|
+
);
|
|
3586
|
+
}
|
|
3587
|
+
|
|
3588
|
+
const nodes: SupplyData[] = [];
|
|
3589
|
+
for (const connectedNode of connectedNodes) {
|
|
3590
|
+
const connectedNodeType = workflow.nodeTypes.getByNameAndVersion(
|
|
3591
|
+
connectedNode.type,
|
|
3592
|
+
connectedNode.typeVersion,
|
|
3593
|
+
);
|
|
3594
|
+
|
|
3595
|
+
if (!connectedNodeType) {
|
|
3596
|
+
continue;
|
|
3597
|
+
}
|
|
3598
|
+
|
|
3599
|
+
if (connectedNodeType && !connectedNodeType.supplyData) {
|
|
3600
|
+
throw new NodeOperationError(connectedNode, 'Node does not have a `supplyData` method defined', {
|
|
3601
|
+
itemIndex,
|
|
3602
|
+
});
|
|
3603
|
+
} else {
|
|
3604
|
+
try {
|
|
3605
|
+
await additionalData.hooks?.executeHookFunctions('nodeExecuteBefore', [connectedNode.name, {
|
|
3606
|
+
startTime: startTime,
|
|
3607
|
+
executionTime: new Date().getTime() - startTime
|
|
3608
|
+
}]);
|
|
3609
|
+
|
|
3610
|
+
const context = getSupplyDataFunctions(
|
|
3611
|
+
workflow,
|
|
3612
|
+
runExecutionData,
|
|
3613
|
+
runIndex,
|
|
3614
|
+
connectionInputData,
|
|
3615
|
+
inputData,
|
|
3616
|
+
connectedNode,
|
|
3617
|
+
additionalData,
|
|
3618
|
+
mode,
|
|
3619
|
+
nodeTypeData,
|
|
3620
|
+
closeFunctions,
|
|
3621
|
+
);
|
|
3622
|
+
|
|
3623
|
+
const supplyData = await connectedNodeType.supplyData?.call(context, itemIndex);
|
|
3624
|
+
if (supplyData) {
|
|
3625
|
+
nodes.push(supplyData);
|
|
3626
|
+
|
|
3627
|
+
if (supplyData.closeFunction) {
|
|
3628
|
+
closeFunctions.push(supplyData.closeFunction);
|
|
3629
|
+
}
|
|
3630
|
+
|
|
3631
|
+
const taskData: ITaskData = {
|
|
3632
|
+
startTime: startTime,
|
|
3633
|
+
executionTime: new Date().getTime() - startTime
|
|
3634
|
+
};
|
|
3635
|
+
|
|
3636
|
+
if (!Object.prototype.hasOwnProperty.call(runExecutionData.resultData.runData, connectedNode.name)) {
|
|
3637
|
+
runExecutionData.resultData.runData[connectedNode.name] = [];
|
|
3638
|
+
}
|
|
3639
|
+
|
|
3640
|
+
runExecutionData.resultData.runData[connectedNode.name][runIndex] = taskData;
|
|
3641
|
+
|
|
3642
|
+
await additionalData.hooks?.executeHookFunctions('nodeExecuteAfter', [
|
|
3643
|
+
connectedNode.name,
|
|
3644
|
+
taskData,
|
|
3645
|
+
runExecutionData
|
|
3646
|
+
]);
|
|
3647
|
+
}
|
|
3648
|
+
} catch (error: any) {
|
|
3649
|
+
const taskData: ITaskData = {
|
|
3650
|
+
startTime: startTime,
|
|
3651
|
+
executionTime: new Date().getTime() - startTime,
|
|
3652
|
+
error: error
|
|
3653
|
+
};
|
|
3654
|
+
runExecutionData.resultData.runData[connectedNode.name][runIndex] = taskData;
|
|
3655
|
+
|
|
3656
|
+
await additionalData.hooks?.executeHookFunctions('nodeExecuteAfter', [
|
|
3657
|
+
connectedNode.name,
|
|
3658
|
+
taskData,
|
|
3659
|
+
runExecutionData
|
|
3660
|
+
]);
|
|
3661
|
+
|
|
3662
|
+
throw new NodeOperationError(connectedNode, `Error in sub-node ${connectedNode.name}`, {
|
|
3663
|
+
itemIndex,
|
|
3664
|
+
description: error?.message ?? 'Unknown error',
|
|
3665
|
+
});
|
|
3666
|
+
}
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3669
|
+
|
|
3670
|
+
return inputConfiguration.maxConnections === 1
|
|
3671
|
+
? (nodes || [])[0]?.response
|
|
3672
|
+
: nodes.map((node) => node.response);
|
|
3673
|
+
}
|
|
3674
|
+
|
|
3675
|
+
function getNodeInputs(
|
|
3676
|
+
workflow: Workflow,
|
|
3677
|
+
node: INode,
|
|
3678
|
+
nodeTypeData: INodeTypeDescription,
|
|
3679
|
+
): Array<NodeConnectionType | INodeInputConfiguration> {
|
|
3680
|
+
if (Array.isArray(nodeTypeData?.inputs)) {
|
|
3681
|
+
return nodeTypeData.inputs as Array<NodeConnectionType | INodeInputConfiguration>;
|
|
3682
|
+
}
|
|
3683
|
+
|
|
3684
|
+
// Calculate the outputs dynamically
|
|
3685
|
+
try {
|
|
3686
|
+
return (workflow.expression.getSimpleParameterValue(
|
|
3687
|
+
node,
|
|
3688
|
+
nodeTypeData.inputs,
|
|
3689
|
+
'internal',
|
|
3690
|
+
{},
|
|
3691
|
+
) || []) as NodeConnectionType[];
|
|
3692
|
+
} catch (e) {
|
|
3693
|
+
console.warn('Could not calculate inputs dynamically for node: ', node.name);
|
|
3694
|
+
return [];
|
|
3695
|
+
}
|
|
3696
|
+
}
|
|
3697
|
+
|
|
3698
|
+
function getConnectedNodes(workflow: Workflow, node: INode, connectionType: NodeConnectionType) {
|
|
3699
|
+
return workflow
|
|
3700
|
+
.getParentNodes(node.name, connectionType, 1)
|
|
3701
|
+
.map((nodeName) => workflow.getNode(nodeName))
|
|
3702
|
+
.filter((node) => !!node)
|
|
3703
|
+
.filter((node) => node.disabled !== true);
|
|
3704
|
+
}
|