mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
🔀 Merge master
This commit is contained in:
commit
fc95c00c72
|
@ -2,6 +2,49 @@
|
|||
|
||||
This list shows all the versions which include breaking changes and how to upgrade.
|
||||
|
||||
## 0.135.0
|
||||
|
||||
### What changed?
|
||||
|
||||
The in-node core methods for credentials and binary data have changed.
|
||||
|
||||
### When is action necessary?
|
||||
|
||||
If you are using custom n8n nodes.
|
||||
|
||||
### How to upgrade:
|
||||
|
||||
1. The method `this.getCredentials(myNodeCredentials)` is now async. So `await` has to be added in front of it.
|
||||
|
||||
Example:
|
||||
|
||||
```typescript
|
||||
// Before 0.135.0:
|
||||
const credentials = this.getCredentials(credentialTypeName);
|
||||
|
||||
// From 0.135.0:
|
||||
const credentials = await this.getCredentials(myNodeCredentials);
|
||||
```
|
||||
|
||||
2. Binary data should not get accessed directly anymore, instead the method `await this.helpers.getBinaryDataBuffer(itemIndex, binaryPropertyName)` has to be used.
|
||||
|
||||
Example:
|
||||
|
||||
```typescript
|
||||
|
||||
const items = this.getInputData();
|
||||
|
||||
for (const i = 0; i < items.length; i++) {
|
||||
const item = items[i].binary as IBinaryKeyData;
|
||||
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string;
|
||||
const binaryData = item[binaryPropertyName] as IBinaryData;
|
||||
// Before 0.135.0:
|
||||
const binaryDataBuffer = Buffer.from(binaryData.data, BINARY_ENCODING);
|
||||
// From 0.135.0:
|
||||
const binaryDataBuffer = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName);
|
||||
}
|
||||
```
|
||||
|
||||
## 0.131.0
|
||||
|
||||
### What changed?
|
||||
|
|
|
@ -155,10 +155,7 @@ export class Execute extends Command {
|
|||
}
|
||||
|
||||
try {
|
||||
const credentials = await WorkflowCredentials(workflowData!.nodes);
|
||||
|
||||
const runData: IWorkflowExecutionDataProcess = {
|
||||
credentials,
|
||||
executionMode: 'cli',
|
||||
startNodes: [startNode.name],
|
||||
workflowData: workflowData!,
|
||||
|
|
|
@ -635,10 +635,8 @@ export class ExecuteBatch extends Command {
|
|||
|
||||
|
||||
try {
|
||||
const credentials = await WorkflowCredentials(workflowData!.nodes);
|
||||
|
||||
const runData: IWorkflowExecutionDataProcess = {
|
||||
credentials,
|
||||
executionMode: 'cli',
|
||||
startNodes: [startNode!.name],
|
||||
workflowData: workflowData!,
|
||||
|
|
|
@ -21,7 +21,7 @@ import {
|
|||
} from 'n8n-workflow';
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as glob from 'glob-promise';
|
||||
import * as glob from 'fast-glob';
|
||||
import * as path from 'path';
|
||||
|
||||
export class ImportCredentialsCommand extends Command {
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
} from 'n8n-workflow';
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as glob from 'glob-promise';
|
||||
import * as glob from 'fast-glob';
|
||||
import * as path from 'path';
|
||||
import {
|
||||
UserSettings,
|
||||
|
|
|
@ -22,10 +22,11 @@ import {
|
|||
NodeTypes,
|
||||
Server,
|
||||
TestWebhooks,
|
||||
WaitTracker,
|
||||
} from '../src';
|
||||
import { IDataObject } from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
import {
|
||||
getLogger,
|
||||
} from '../src/Logger';
|
||||
|
||||
|
@ -284,6 +285,8 @@ export class Start extends Command {
|
|||
activeWorkflowRunner = ActiveWorkflowRunner.getInstance();
|
||||
await activeWorkflowRunner.init();
|
||||
|
||||
const waitTracker = WaitTracker();
|
||||
|
||||
const editorUrl = GenericHelpers.getBaseUrl();
|
||||
this.log(`\nEditor is now accessible via:\n${editorUrl}`);
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ import {
|
|||
WorkflowExecuteAdditionalData,
|
||||
} from '../src';
|
||||
|
||||
import {
|
||||
import {
|
||||
getLogger,
|
||||
} from '../src/Logger';
|
||||
|
||||
|
@ -148,10 +148,9 @@ export class Worker extends Command {
|
|||
|
||||
const workflow = new Workflow({ id: currentExecutionDb.workflowData.id as string, name: currentExecutionDb.workflowData.name, nodes: currentExecutionDb.workflowData!.nodes, connections: currentExecutionDb.workflowData!.connections, active: currentExecutionDb.workflowData!.active, nodeTypes, staticData, settings: currentExecutionDb.workflowData!.settings });
|
||||
|
||||
const credentials = await WorkflowCredentials(currentExecutionDb.workflowData.nodes);
|
||||
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(credentials, undefined, executionTimeoutTimestamp);
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(undefined, executionTimeoutTimestamp);
|
||||
additionalData.hooks = WorkflowExecuteAdditionalData.getWorkflowHooksWorkerExecuter(currentExecutionDb.mode, job.data.executionId, currentExecutionDb.workflowData, { retryOf: currentExecutionDb.retryOf as string });
|
||||
additionalData.executionId = jobData.executionId;
|
||||
|
||||
let workflowExecute: WorkflowExecute;
|
||||
let workflowRun: PCancelable<IRun>;
|
||||
|
|
|
@ -489,6 +489,12 @@ const config = convict({
|
|||
env: 'N8N_ENDPOINT_WEBHOOK',
|
||||
doc: 'Path for webhook endpoint',
|
||||
},
|
||||
webhookWaiting: {
|
||||
format: String,
|
||||
default: 'webhook-waiting',
|
||||
env: 'N8N_ENDPOINT_WEBHOOK_WAIT',
|
||||
doc: 'Path for waiting-webhook endpoint',
|
||||
},
|
||||
webhookTest: {
|
||||
format: String,
|
||||
default: 'webhook-test',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n",
|
||||
"version": "0.134.0",
|
||||
"version": "0.135.1",
|
||||
"description": "n8n Workflow Automation Tool",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -101,8 +101,8 @@
|
|||
"csrf": "^3.1.0",
|
||||
"dotenv": "^8.0.0",
|
||||
"express": "^4.16.4",
|
||||
"fast-glob": "^3.2.5",
|
||||
"flatted": "^2.0.0",
|
||||
"glob-promise": "^3.4.0",
|
||||
"google-timezones-json": "^1.0.2",
|
||||
"inquirer": "^7.0.1",
|
||||
"json-diff": "^0.5.4",
|
||||
|
@ -111,10 +111,10 @@
|
|||
"localtunnel": "^2.0.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"mysql2": "~2.2.0",
|
||||
"n8n-core": "~0.79.0",
|
||||
"n8n-editor-ui": "~0.102.0",
|
||||
"n8n-nodes-base": "~0.131.0",
|
||||
"n8n-workflow": "~0.64.0",
|
||||
"n8n-core": "~0.80.0",
|
||||
"n8n-editor-ui": "~0.103.0",
|
||||
"n8n-nodes-base": "~0.132.0",
|
||||
"n8n-workflow": "~0.65.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"open": "^7.0.0",
|
||||
"pg": "^8.3.0",
|
||||
|
|
|
@ -35,32 +35,43 @@ export class ActiveExecutions {
|
|||
* @returns {string}
|
||||
* @memberof ActiveExecutions
|
||||
*/
|
||||
async add(executionData: IWorkflowExecutionDataProcess, process?: ChildProcess): Promise<string> {
|
||||
async add(executionData: IWorkflowExecutionDataProcess, process?: ChildProcess, executionId?: string): Promise<string> {
|
||||
|
||||
const fullExecutionData: IExecutionDb = {
|
||||
data: executionData.executionData!,
|
||||
mode: executionData.executionMode,
|
||||
finished: false,
|
||||
startedAt: new Date(),
|
||||
workflowData: executionData.workflowData,
|
||||
};
|
||||
if (executionId === undefined) {
|
||||
// Is a new execution so save in DB
|
||||
|
||||
if (executionData.retryOf !== undefined) {
|
||||
fullExecutionData.retryOf = executionData.retryOf.toString();
|
||||
const fullExecutionData: IExecutionDb = {
|
||||
data: executionData.executionData!,
|
||||
mode: executionData.executionMode,
|
||||
finished: false,
|
||||
startedAt: new Date(),
|
||||
workflowData: executionData.workflowData,
|
||||
};
|
||||
|
||||
if (executionData.retryOf !== undefined) {
|
||||
fullExecutionData.retryOf = executionData.retryOf.toString();
|
||||
}
|
||||
|
||||
if (executionData.workflowData.id !== undefined && WorkflowHelpers.isWorkflowIdValid(executionData.workflowData.id.toString()) === true) {
|
||||
fullExecutionData.workflowId = executionData.workflowData.id.toString();
|
||||
}
|
||||
|
||||
const execution = ResponseHelper.flattenExecutionData(fullExecutionData);
|
||||
|
||||
const executionResult = await Db.collections.Execution!.save(execution as IExecutionFlattedDb);
|
||||
executionId = typeof executionResult.id === "object" ? executionResult.id!.toString() : executionResult.id + "";
|
||||
} else {
|
||||
// Is an existing execution we want to finish so update in DB
|
||||
|
||||
const execution = {
|
||||
id: executionId,
|
||||
waitTill: null,
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
await Db.collections.Execution!.update(executionId, execution);
|
||||
}
|
||||
|
||||
if (executionData.workflowData.id !== undefined && WorkflowHelpers.isWorkflowIdValid(executionData.workflowData.id.toString()) === true) {
|
||||
fullExecutionData.workflowId = executionData.workflowData.id.toString();
|
||||
}
|
||||
|
||||
const execution = ResponseHelper.flattenExecutionData(fullExecutionData);
|
||||
|
||||
// Save the Execution in DB
|
||||
const executionResult = await Db.collections.Execution!.save(execution as IExecutionFlattedDb);
|
||||
|
||||
// @ts-ignore
|
||||
const executionId = typeof executionResult.id === "object" ? executionResult.id!.toString() : executionResult.id + "";
|
||||
|
||||
this.activeExecutions[executionId] = {
|
||||
executionData,
|
||||
process,
|
||||
|
|
|
@ -194,9 +194,7 @@ export class ActiveWorkflowRunner {
|
|||
const nodeTypes = NodeTypes();
|
||||
const workflow = new Workflow({ id: webhook.workflowId.toString(), name: workflowData.name, nodes: workflowData.nodes, connections: workflowData.connections, active: workflowData.active, nodeTypes, staticData: workflowData.staticData, settings: workflowData.settings });
|
||||
|
||||
const credentials = await WorkflowCredentials([workflow.getNode(webhook.node as string) as INode]);
|
||||
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(credentials);
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase();
|
||||
|
||||
const webhookData = NodeHelpers.getNodeWebhooks(workflow, workflow.getNode(webhook.node as string) as INode, additionalData).filter((webhook) => {
|
||||
return (webhook.httpMethod === httpMethod && webhook.path === path);
|
||||
|
@ -213,7 +211,7 @@ export class ActiveWorkflowRunner {
|
|||
return new Promise((resolve, reject) => {
|
||||
const executionMode = 'webhook';
|
||||
//@ts-ignore
|
||||
WebhookHelpers.executeWebhook(workflow, webhookData, workflowData, workflowStartNode, executionMode, undefined, req, res, (error: Error | null, data: object) => {
|
||||
WebhookHelpers.executeWebhook(workflow, webhookData, workflowData, workflowStartNode, executionMode, undefined, undefined, undefined, req, res, (error: Error | null, data: object) => {
|
||||
if (error !== null) {
|
||||
return reject(error);
|
||||
}
|
||||
|
@ -286,7 +284,7 @@ export class ActiveWorkflowRunner {
|
|||
* @memberof ActiveWorkflowRunner
|
||||
*/
|
||||
async addWorkflowWebhooks(workflow: Workflow, additionalData: IWorkflowExecuteAdditionalDataWorkflow, mode: WorkflowExecuteMode, activation: WorkflowActivateMode): Promise<void> {
|
||||
const webhooks = WebhookHelpers.getWorkflowWebhooks(workflow, additionalData);
|
||||
const webhooks = WebhookHelpers.getWorkflowWebhooks(workflow, additionalData, undefined, true);
|
||||
let path = '' as string | undefined;
|
||||
|
||||
for (const webhookData of webhooks) {
|
||||
|
@ -370,10 +368,9 @@ export class ActiveWorkflowRunner {
|
|||
|
||||
const mode = 'internal';
|
||||
|
||||
const credentials = await WorkflowCredentials(workflowData.nodes);
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(credentials);
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase();
|
||||
|
||||
const webhooks = WebhookHelpers.getWorkflowWebhooks(workflow, additionalData);
|
||||
const webhooks = WebhookHelpers.getWorkflowWebhooks(workflow, additionalData, undefined, true);
|
||||
|
||||
for (const webhookData of webhooks) {
|
||||
await workflow.runWebhookMethod('delete', webhookData, NodeExecuteFunctions, mode, 'update', false);
|
||||
|
@ -423,7 +420,6 @@ export class ActiveWorkflowRunner {
|
|||
|
||||
// Start the workflow
|
||||
const runData: IWorkflowExecutionDataProcess = {
|
||||
credentials: additionalData.credentials,
|
||||
executionMode: mode,
|
||||
executionData,
|
||||
workflowData,
|
||||
|
@ -510,8 +506,7 @@ export class ActiveWorkflowRunner {
|
|||
}
|
||||
|
||||
const mode = 'trigger';
|
||||
const credentials = await WorkflowCredentials(workflowData.nodes);
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(credentials);
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase();
|
||||
const getTriggerFunctions = this.getExecuteTriggerFunctions(workflowData, additionalData, mode, activation);
|
||||
const getPollFunctions = this.getExecutePollFunctions(workflowData, additionalData, mode, activation);
|
||||
|
||||
|
|
|
@ -48,16 +48,21 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||
* @returns {Credentials}
|
||||
* @memberof CredentialsHelper
|
||||
*/
|
||||
getCredentials(name: string, type: string): Credentials {
|
||||
if (!this.workflowCredentials[type]) {
|
||||
async getCredentials(name: string, type: string): Promise<Credentials> {
|
||||
|
||||
const credentialsDb = await Db.collections.Credentials?.find({type});
|
||||
|
||||
if (credentialsDb === undefined || credentialsDb.length === 0) {
|
||||
throw new Error(`No credentials of type "${type}" exist.`);
|
||||
}
|
||||
if (!this.workflowCredentials[type][name]) {
|
||||
|
||||
const credential = credentialsDb.find(credential => credential.name === name);
|
||||
|
||||
if (credential === undefined) {
|
||||
throw new Error(`No credentials with name "${name}" exist for type "${type}".`);
|
||||
}
|
||||
const credentialData = this.workflowCredentials[type][name];
|
||||
|
||||
return new Credentials(credentialData.name, credentialData.type, credentialData.nodesAccess, credentialData.data);
|
||||
|
||||
return new Credentials(credential.name, credential.type, credential.nodesAccess, credential.data);
|
||||
}
|
||||
|
||||
|
||||
|
@ -102,8 +107,8 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||
* @returns {ICredentialDataDecryptedObject}
|
||||
* @memberof CredentialsHelper
|
||||
*/
|
||||
getDecrypted(name: string, type: string, mode: WorkflowExecuteMode, raw?: boolean, expressionResolveValues?: ICredentialsExpressionResolveValues): ICredentialDataDecryptedObject {
|
||||
const credentials = this.getCredentials(name, type);
|
||||
async getDecrypted(name: string, type: string, mode: WorkflowExecuteMode, raw?: boolean, expressionResolveValues?: ICredentialsExpressionResolveValues): Promise<ICredentialDataDecryptedObject> {
|
||||
const credentials = await this.getCredentials(name, type);
|
||||
|
||||
const decryptedDataOriginal = credentials.getData(this.encryptionKey);
|
||||
|
||||
|
@ -138,7 +143,7 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||
if (expressionResolveValues) {
|
||||
try {
|
||||
const workflow = new Workflow({ nodes: Object.values(expressionResolveValues.workflow.nodes), connections: expressionResolveValues.workflow.connectionsBySourceNode, active: false, nodeTypes: expressionResolveValues.workflow.nodeTypes });
|
||||
decryptedData = workflow.expression.getParameterValue(decryptedData as INodeParameters, expressionResolveValues.runExecutionData, expressionResolveValues.runIndex, expressionResolveValues.itemIndex, expressionResolveValues.node.name, expressionResolveValues.connectionInputData, mode, false, decryptedData) as ICredentialDataDecryptedObject;
|
||||
decryptedData = workflow.expression.getParameterValue(decryptedData as INodeParameters, expressionResolveValues.runExecutionData, expressionResolveValues.runIndex, expressionResolveValues.itemIndex, expressionResolveValues.node.name, expressionResolveValues.connectionInputData, mode, {}, false, decryptedData) as ICredentialDataDecryptedObject;
|
||||
} catch (e) {
|
||||
e.message += ' [Error resolving credentials]';
|
||||
throw e;
|
||||
|
@ -155,7 +160,7 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||
const workflow = new Workflow({ nodes: [node!], connections: {}, active: false, nodeTypes: mockNodeTypes });
|
||||
|
||||
// Resolve expressions if any are set
|
||||
decryptedData = workflow.expression.getComplexParameterValue(node!, decryptedData as INodeParameters, mode, undefined, decryptedData) as ICredentialDataDecryptedObject;
|
||||
decryptedData = workflow.expression.getComplexParameterValue(node!, decryptedData as INodeParameters, mode, {}, undefined, decryptedData) as ICredentialDataDecryptedObject;
|
||||
}
|
||||
|
||||
// Load and apply the credentials overwrites if any exist
|
||||
|
|
|
@ -150,6 +150,7 @@ export interface IExecutionBase {
|
|||
// Data in regular format with references
|
||||
export interface IExecutionDb extends IExecutionBase {
|
||||
data: IRunExecutionData;
|
||||
waitTill?: Date;
|
||||
workflowData?: IWorkflowBase;
|
||||
}
|
||||
|
||||
|
@ -163,6 +164,7 @@ export interface IExecutionResponse extends IExecutionBase {
|
|||
data: IRunExecutionData;
|
||||
retryOf?: string;
|
||||
retrySuccessId?: string;
|
||||
waitTill?: Date;
|
||||
workflowData: IWorkflowBase;
|
||||
}
|
||||
|
||||
|
@ -176,6 +178,7 @@ export interface IExecutionFlatted extends IExecutionBase {
|
|||
export interface IExecutionFlattedDb extends IExecutionBase {
|
||||
id: number | string;
|
||||
data: string;
|
||||
waitTill?: Date | null;
|
||||
workflowData: IWorkflowBase;
|
||||
}
|
||||
|
||||
|
@ -204,6 +207,7 @@ export interface IExecutionsSummary {
|
|||
mode: WorkflowExecuteMode;
|
||||
retryOf?: string;
|
||||
retrySuccessId?: string;
|
||||
waitTill?: Date;
|
||||
startedAt: Date;
|
||||
stoppedAt?: Date;
|
||||
workflowId: string;
|
||||
|
@ -457,7 +461,6 @@ export interface IProcessMessageDataHook {
|
|||
}
|
||||
|
||||
export interface IWorkflowExecutionDataProcess {
|
||||
credentials: IWorkflowCredentials;
|
||||
destinationNode?: string;
|
||||
executionMode: WorkflowExecuteMode;
|
||||
executionData?: IRunExecutionData;
|
||||
|
|
|
@ -22,8 +22,8 @@ import {
|
|||
readdir as fsReaddir,
|
||||
readFile as fsReadFile,
|
||||
stat as fsStat,
|
||||
} from 'fs/promises';
|
||||
import * as glob from 'glob-promise';
|
||||
} from 'fs/promises';
|
||||
import * as glob from 'fast-glob';
|
||||
import * as path from 'path';
|
||||
|
||||
const CUSTOM_NODES_CATEGORY = 'Custom Nodes';
|
||||
|
|
|
@ -163,6 +163,7 @@ export function flattenExecutionData(fullExecutionData: IExecutionDb): IExecutio
|
|||
const returnData: IExecutionFlatted = Object.assign({}, {
|
||||
data: stringify(fullExecutionData.data),
|
||||
mode: fullExecutionData.mode,
|
||||
waitTill: fullExecutionData.waitTill,
|
||||
startedAt: fullExecutionData.startedAt,
|
||||
stoppedAt: fullExecutionData.stoppedAt,
|
||||
finished: fullExecutionData.finished ? fullExecutionData.finished : false,
|
||||
|
@ -200,6 +201,7 @@ export function unflattenExecutionData(fullExecutionData: IExecutionFlattedDb):
|
|||
workflowData: fullExecutionData.workflowData as IWorkflowDb,
|
||||
data: parse(fullExecutionData.data),
|
||||
mode: fullExecutionData.mode,
|
||||
waitTill: fullExecutionData.waitTill ? fullExecutionData.waitTill : undefined,
|
||||
startedAt: fullExecutionData.startedAt,
|
||||
stoppedAt: fullExecutionData.stoppedAt,
|
||||
finished: fullExecutionData.finished ? fullExecutionData.finished : false,
|
||||
|
|
|
@ -64,9 +64,11 @@ import {
|
|||
Push,
|
||||
ResponseHelper,
|
||||
TestWebhooks,
|
||||
WaitingWebhooks,
|
||||
WaitTracker,
|
||||
WaitTrackerClass,
|
||||
WebhookHelpers,
|
||||
WebhookServer,
|
||||
WorkflowCredentials,
|
||||
WorkflowExecuteAdditionalData,
|
||||
WorkflowHelpers,
|
||||
WorkflowRunner,
|
||||
|
@ -97,6 +99,7 @@ import {
|
|||
import {
|
||||
FindManyOptions,
|
||||
FindOneOptions,
|
||||
IsNull,
|
||||
LessThanOrEqual,
|
||||
Not,
|
||||
} from 'typeorm';
|
||||
|
@ -125,9 +128,11 @@ class App {
|
|||
activeWorkflowRunner: ActiveWorkflowRunner.ActiveWorkflowRunner;
|
||||
testWebhooks: TestWebhooks.TestWebhooks;
|
||||
endpointWebhook: string;
|
||||
endpointWebhookWaiting: string;
|
||||
endpointWebhookTest: string;
|
||||
endpointPresetCredentials: string;
|
||||
externalHooks: IExternalHooksClass;
|
||||
waitTracker: WaitTrackerClass;
|
||||
defaultWorkflowName: string;
|
||||
saveDataErrorExecution: string;
|
||||
saveDataSuccessExecution: string;
|
||||
|
@ -151,6 +156,7 @@ class App {
|
|||
this.app = express();
|
||||
|
||||
this.endpointWebhook = config.get('endpoints.webhook') as string;
|
||||
this.endpointWebhookWaiting = config.get('endpoints.webhookWaiting') as string;
|
||||
this.endpointWebhookTest = config.get('endpoints.webhookTest') as string;
|
||||
|
||||
this.defaultWorkflowName = config.get('workflows.defaultName') as string;
|
||||
|
@ -169,6 +175,7 @@ class App {
|
|||
this.push = Push.getInstance();
|
||||
|
||||
this.activeExecutionsInstance = ActiveExecutions.getInstance();
|
||||
this.waitTracker = WaitTracker();
|
||||
|
||||
this.protocol = config.get('protocol');
|
||||
this.sslKey = config.get('ssl_key');
|
||||
|
@ -621,7 +628,6 @@ class App {
|
|||
return { name: `${nameToReturn} ${maxSuffix + 1}` };
|
||||
}));
|
||||
|
||||
|
||||
// Returns a specific workflow
|
||||
this.app.get(`/${this.restEndpoint}/workflows/:id`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<WorkflowEntity | undefined> => {
|
||||
const workflow = await Db.collections.Workflow!.findOne(req.params.id, { relations: ['tags'] });
|
||||
|
@ -764,8 +770,7 @@ class App {
|
|||
|
||||
// If webhooks nodes exist and are active we have to wait for till we receive a call
|
||||
if (runData === undefined || startNodes === undefined || startNodes.length === 0 || destinationNode === undefined) {
|
||||
const credentials = await WorkflowCredentials(workflowData.nodes);
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(credentials);
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase();
|
||||
const nodeTypes = NodeTypes();
|
||||
const workflowInstance = new Workflow({ id: workflowData.id, name: workflowData.name, nodes: workflowData.nodes, connections: workflowData.connections, active: false, nodeTypes, staticData: undefined, settings: workflowData.settings });
|
||||
const needsWebhook = await this.testWebhooks.needsWebhookData(workflowData, workflowInstance, additionalData, executionMode, activationMode, sessionId, destinationNode);
|
||||
|
@ -779,11 +784,8 @@ class App {
|
|||
// For manual testing always set to not active
|
||||
workflowData.active = false;
|
||||
|
||||
const credentials = await WorkflowCredentials(workflowData.nodes);
|
||||
|
||||
// Start the workflow
|
||||
const data: IWorkflowExecutionDataProcess = {
|
||||
credentials,
|
||||
destinationNode,
|
||||
executionMode,
|
||||
runData,
|
||||
|
@ -880,9 +882,7 @@ class App {
|
|||
// @ts-ignore
|
||||
const loadDataInstance = new LoadNodeParameterOptions(nodeType, nodeTypes, path, JSON.parse('' + req.query.currentNodeParameters), credentials!);
|
||||
|
||||
const workflowData = loadDataInstance.getWorkflowData() as IWorkflowBase;
|
||||
const workflowCredentials = await WorkflowCredentials(workflowData.nodes);
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(workflowCredentials, currentNodeParameters);
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(currentNodeParameters);
|
||||
|
||||
return loadDataInstance.getOptions(methodName, additionalData);
|
||||
}));
|
||||
|
@ -1259,15 +1259,9 @@ class App {
|
|||
return '';
|
||||
}
|
||||
|
||||
// Decrypt the currently saved credentials
|
||||
const workflowCredentials: IWorkflowCredentials = {
|
||||
[result.type as string]: {
|
||||
[result.name as string]: result as ICredentialsEncrypted,
|
||||
},
|
||||
};
|
||||
const mode: WorkflowExecuteMode = 'internal';
|
||||
const credentialsHelper = new CredentialsHelper(workflowCredentials, encryptionKey);
|
||||
const decryptedDataOriginal = credentialsHelper.getDecrypted(result.name, result.type, mode, true);
|
||||
const credentialsHelper = new CredentialsHelper(encryptionKey);
|
||||
const decryptedDataOriginal = await credentialsHelper.getDecrypted(result.name, result.type, mode, true);
|
||||
const oauthCredentials = credentialsHelper.applyDefaultsAndOverwrites(decryptedDataOriginal, result.type, mode);
|
||||
|
||||
const signatureMethod = _.get(oauthCredentials, 'signatureMethod') as string;
|
||||
|
@ -1351,6 +1345,7 @@ class App {
|
|||
return ResponseHelper.sendErrorResponse(res, errorResponse);
|
||||
}
|
||||
|
||||
|
||||
// Decrypt the currently saved credentials
|
||||
const workflowCredentials: IWorkflowCredentials = {
|
||||
[result.type as string]: {
|
||||
|
@ -1358,10 +1353,10 @@ class App {
|
|||
},
|
||||
};
|
||||
const mode: WorkflowExecuteMode = 'internal';
|
||||
const credentialsHelper = new CredentialsHelper(workflowCredentials, encryptionKey);
|
||||
const decryptedDataOriginal = credentialsHelper.getDecrypted(result.name, result.type, mode, true);
|
||||
const credentialsHelper = new CredentialsHelper(encryptionKey);
|
||||
const decryptedDataOriginal = await credentialsHelper.getDecrypted(result.name, result.type, mode, true);
|
||||
const oauthCredentials = credentialsHelper.applyDefaultsAndOverwrites(decryptedDataOriginal, result.type, mode);
|
||||
|
||||
|
||||
const options: OptionsWithUrl = {
|
||||
method: 'POST',
|
||||
url: _.get(oauthCredentials, 'accessTokenUrl') as string,
|
||||
|
@ -1427,15 +1422,9 @@ class App {
|
|||
return '';
|
||||
}
|
||||
|
||||
// Decrypt the currently saved credentials
|
||||
const workflowCredentials: IWorkflowCredentials = {
|
||||
[result.type as string]: {
|
||||
[result.name as string]: result as ICredentialsEncrypted,
|
||||
},
|
||||
};
|
||||
const mode: WorkflowExecuteMode = 'internal';
|
||||
const credentialsHelper = new CredentialsHelper(workflowCredentials, encryptionKey);
|
||||
const decryptedDataOriginal = credentialsHelper.getDecrypted(result.name, result.type, mode, true);
|
||||
const credentialsHelper = new CredentialsHelper(encryptionKey);
|
||||
const decryptedDataOriginal = await credentialsHelper.getDecrypted(result.name, result.type, mode, true);
|
||||
const oauthCredentials = credentialsHelper.applyDefaultsAndOverwrites(decryptedDataOriginal, result.type, mode);
|
||||
|
||||
const token = new csrf();
|
||||
|
@ -1534,11 +1523,12 @@ class App {
|
|||
[result.name as string]: result as ICredentialsEncrypted,
|
||||
},
|
||||
};
|
||||
|
||||
const mode: WorkflowExecuteMode = 'internal';
|
||||
const credentialsHelper = new CredentialsHelper(workflowCredentials, encryptionKey);
|
||||
const decryptedDataOriginal = credentialsHelper.getDecrypted(result.name, result.type, mode, true);
|
||||
const credentialsHelper = new CredentialsHelper(encryptionKey);
|
||||
const decryptedDataOriginal = await credentialsHelper.getDecrypted(result.name, result.type, mode, true);
|
||||
const oauthCredentials = credentialsHelper.applyDefaultsAndOverwrites(decryptedDataOriginal, result.type, mode);
|
||||
|
||||
|
||||
const token = new csrf();
|
||||
if (decryptedDataOriginal.csrfSecret === undefined || !token.verify(decryptedDataOriginal.csrfSecret as string, state.token)) {
|
||||
const errorResponse = new ResponseHelper.ResponseError('The OAuth2 callback state is invalid!', undefined, 404);
|
||||
|
@ -1638,6 +1628,9 @@ class App {
|
|||
executingWorkflowIds.push(...this.activeExecutionsInstance.getActiveExecutions().map(execution => execution.id.toString()) as string[]);
|
||||
|
||||
const countFilter = JSON.parse(JSON.stringify(filter));
|
||||
if (countFilter.waitTill !== undefined) {
|
||||
countFilter.waitTill = Not(IsNull());
|
||||
}
|
||||
countFilter.id = Not(In(executingWorkflowIds));
|
||||
|
||||
const resultsQuery = await Db.collections.Execution!
|
||||
|
@ -1648,6 +1641,7 @@ class App {
|
|||
'execution.mode',
|
||||
'execution.retryOf',
|
||||
'execution.retrySuccessId',
|
||||
'execution.waitTill',
|
||||
'execution.startedAt',
|
||||
'execution.stoppedAt',
|
||||
'execution.workflowData',
|
||||
|
@ -1656,7 +1650,14 @@ class App {
|
|||
.take(limit);
|
||||
|
||||
Object.keys(filter).forEach((filterField) => {
|
||||
resultsQuery.andWhere(`execution.${filterField} = :${filterField}`, {[filterField]: filter[filterField]});
|
||||
if (filterField === 'waitTill') {
|
||||
resultsQuery.andWhere(`execution.${filterField} is not null`);
|
||||
} else if(filterField === 'finished' && filter[filterField] === false) {
|
||||
resultsQuery.andWhere(`execution.${filterField} = :${filterField}`, {[filterField]: filter[filterField]});
|
||||
resultsQuery.andWhere(`execution.waitTill is null`);
|
||||
} else {
|
||||
resultsQuery.andWhere(`execution.${filterField} = :${filterField}`, {[filterField]: filter[filterField]});
|
||||
}
|
||||
});
|
||||
if (req.query.lastId) {
|
||||
resultsQuery.andWhere(`execution.id < :lastId`, {lastId: req.query.lastId});
|
||||
|
@ -1684,6 +1685,7 @@ class App {
|
|||
mode: result.mode,
|
||||
retryOf: result.retryOf ? result.retryOf.toString() : undefined,
|
||||
retrySuccessId: result.retrySuccessId ? result.retrySuccessId.toString() : undefined,
|
||||
waitTill: result.waitTill as Date | undefined,
|
||||
startedAt: result.startedAt,
|
||||
stoppedAt: result.stoppedAt,
|
||||
workflowId: result.workflowData!.id ? result.workflowData!.id!.toString() : '',
|
||||
|
@ -1735,13 +1737,10 @@ class App {
|
|||
|
||||
const executionMode = 'retry';
|
||||
|
||||
const credentials = await WorkflowCredentials(fullExecutionData.workflowData.nodes);
|
||||
|
||||
fullExecutionData.workflowData.active = false;
|
||||
|
||||
// Start the workflow
|
||||
const data: IWorkflowExecutionDataProcess = {
|
||||
credentials,
|
||||
executionMode,
|
||||
executionData: fullExecutionData.data,
|
||||
retryOf: req.params.id,
|
||||
|
@ -1913,15 +1912,22 @@ class App {
|
|||
// Manual executions should still be stoppable, so
|
||||
// try notifying the `activeExecutions` to stop it.
|
||||
const result = await this.activeExecutionsInstance.stopExecution(req.params.id);
|
||||
if (result !== undefined) {
|
||||
const returnData: IExecutionsStopData = {
|
||||
|
||||
if (result === undefined) {
|
||||
// If active execution could not be found check if it is a waiting one
|
||||
try {
|
||||
return await this.waitTracker.stopExecution(req.params.id);
|
||||
} catch (error) {
|
||||
// Ignore, if it errors as then it is probably a currently running
|
||||
// execution
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
mode: result.mode,
|
||||
startedAt: new Date(result.startedAt),
|
||||
stoppedAt: result.stoppedAt ? new Date(result.stoppedAt) : undefined,
|
||||
stoppedAt: result.stoppedAt ? new Date(result.stoppedAt) : undefined,
|
||||
finished: result.finished,
|
||||
};
|
||||
|
||||
return returnData;
|
||||
} as IExecutionsStopData;
|
||||
}
|
||||
|
||||
const currentJobs = await Queue.getInstance().getJobs(['active', 'waiting']);
|
||||
|
@ -1952,17 +1958,19 @@ class App {
|
|||
// Stopt he execution and wait till it is done and we got the data
|
||||
const result = await this.activeExecutionsInstance.stopExecution(executionId);
|
||||
|
||||
let returnData: IExecutionsStopData;
|
||||
if (result === undefined) {
|
||||
throw new Error(`The execution id "${executionId}" could not be found.`);
|
||||
// If active execution could not be found check if it is a waiting one
|
||||
returnData = await this.waitTracker.stopExecution(executionId);
|
||||
} else {
|
||||
returnData = {
|
||||
mode: result.mode,
|
||||
startedAt: new Date(result.startedAt),
|
||||
stoppedAt: result.stoppedAt ? new Date(result.stoppedAt) : undefined,
|
||||
finished: result.finished,
|
||||
};
|
||||
}
|
||||
|
||||
const returnData: IExecutionsStopData = {
|
||||
mode: result.mode,
|
||||
startedAt: new Date(result.startedAt),
|
||||
stoppedAt: result.stoppedAt ? new Date(result.stoppedAt) : undefined,
|
||||
finished: result.finished,
|
||||
};
|
||||
|
||||
return returnData;
|
||||
}
|
||||
}));
|
||||
|
@ -2008,6 +2016,76 @@ class App {
|
|||
WebhookServer.registerProductionWebhooks.apply(this);
|
||||
}
|
||||
|
||||
// ----------------------------------------
|
||||
// Waiting Webhooks
|
||||
// ----------------------------------------
|
||||
|
||||
const waitingWebhooks = new WaitingWebhooks();
|
||||
|
||||
// HEAD webhook-waiting requests
|
||||
this.app.head(`/${this.endpointWebhookWaiting}/*`, async (req: express.Request, res: express.Response) => {
|
||||
// Cut away the "/webhook-waiting/" to get the registred part of the url
|
||||
const requestUrl = (req as ICustomRequest).parsedUrl!.pathname!.slice(this.endpointWebhookWaiting.length + 2);
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await waitingWebhooks.executeWebhook('HEAD', requestUrl, req, res);
|
||||
} catch (error) {
|
||||
ResponseHelper.sendErrorResponse(res, error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.noWebhookResponse === true) {
|
||||
// Nothing else to do as the response got already sent
|
||||
return;
|
||||
}
|
||||
|
||||
ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode);
|
||||
});
|
||||
|
||||
// GET webhook-waiting requests
|
||||
this.app.get(`/${this.endpointWebhookWaiting}/*`, async (req: express.Request, res: express.Response) => {
|
||||
// Cut away the "/webhook-waiting/" to get the registred part of the url
|
||||
const requestUrl = (req as ICustomRequest).parsedUrl!.pathname!.slice(this.endpointWebhookWaiting.length + 2);
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await waitingWebhooks.executeWebhook('GET', requestUrl, req, res);
|
||||
} catch (error) {
|
||||
ResponseHelper.sendErrorResponse(res, error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.noWebhookResponse === true) {
|
||||
// Nothing else to do as the response got already sent
|
||||
return;
|
||||
}
|
||||
|
||||
ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode);
|
||||
});
|
||||
|
||||
// POST webhook-waiting requests
|
||||
this.app.post(`/${this.endpointWebhookWaiting}/*`, async (req: express.Request, res: express.Response) => {
|
||||
// Cut away the "/webhook-waiting/" to get the registred part of the url
|
||||
const requestUrl = (req as ICustomRequest).parsedUrl!.pathname!.slice(this.endpointWebhookWaiting.length + 2);
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await waitingWebhooks.executeWebhook('POST', requestUrl, req, res);
|
||||
} catch (error) {
|
||||
ResponseHelper.sendErrorResponse(res, error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.noWebhookResponse === true) {
|
||||
// Nothing else to do as the response got already sent
|
||||
return;
|
||||
}
|
||||
|
||||
ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode);
|
||||
});
|
||||
|
||||
|
||||
// HEAD webhook requests (test for UI)
|
||||
this.app.head(`/${this.endpointWebhookTest}/*`, async (req: express.Request, res: express.Response) => {
|
||||
// Cut away the "/webhook-test/" to get the registred part of the url
|
||||
|
|
|
@ -105,7 +105,7 @@ export class TestWebhooks {
|
|||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const executionMode = 'manual';
|
||||
const executionId = await WebhookHelpers.executeWebhook(workflow, webhookData!, this.testWebhookData[webhookKey].workflowData, workflowStartNode, executionMode, this.testWebhookData[webhookKey].sessionId, request, response, (error: Error | null, data: IResponseCallbackData) => {
|
||||
const executionId = await WebhookHelpers.executeWebhook(workflow, webhookData!, this.testWebhookData[webhookKey].workflowData, workflowStartNode, executionMode, this.testWebhookData[webhookKey].sessionId, undefined, undefined, request, response, (error: Error | null, data: IResponseCallbackData) => {
|
||||
if (error !== null) {
|
||||
return reject(error);
|
||||
}
|
||||
|
@ -163,10 +163,9 @@ export class TestWebhooks {
|
|||
* @memberof TestWebhooks
|
||||
*/
|
||||
async needsWebhookData(workflowData: IWorkflowDb, workflow: Workflow, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, activation: WorkflowActivateMode, sessionId?: string, destinationNode?: string): Promise<boolean> {
|
||||
const webhooks = WebhookHelpers.getWorkflowWebhooks(workflow, additionalData, destinationNode);
|
||||
|
||||
if (webhooks.length === 0) {
|
||||
// No Webhooks found
|
||||
const webhooks = WebhookHelpers.getWorkflowWebhooks(workflow, additionalData, destinationNode, true);
|
||||
if (!webhooks.find(webhook => webhook.webhookDescription.restartWebhook !== true)) {
|
||||
// No webhooks found to start a workflow
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
181
packages/cli/src/WaitTracker.ts
Normal file
181
packages/cli/src/WaitTracker.ts
Normal file
|
@ -0,0 +1,181 @@
|
|||
import {
|
||||
ActiveExecutions,
|
||||
DatabaseType,
|
||||
Db,
|
||||
GenericHelpers,
|
||||
IExecutionFlattedDb,
|
||||
IExecutionsStopData,
|
||||
IWorkflowExecutionDataProcess,
|
||||
ResponseHelper,
|
||||
WorkflowCredentials,
|
||||
WorkflowRunner,
|
||||
} from '.';
|
||||
|
||||
import {
|
||||
IRun,
|
||||
LoggerProxy as Logger,
|
||||
WorkflowOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
FindManyOptions,
|
||||
LessThanOrEqual,
|
||||
ObjectLiteral,
|
||||
} from 'typeorm';
|
||||
|
||||
import { DateUtils } from 'typeorm/util/DateUtils';
|
||||
|
||||
|
||||
export class WaitTrackerClass {
|
||||
activeExecutionsInstance: ActiveExecutions.ActiveExecutions;
|
||||
|
||||
private waitingExecutions: {
|
||||
[key: string]: {
|
||||
executionId: string,
|
||||
timer: NodeJS.Timeout,
|
||||
};
|
||||
} = {};
|
||||
|
||||
mainTimer: NodeJS.Timeout;
|
||||
|
||||
|
||||
constructor() {
|
||||
this.activeExecutionsInstance = ActiveExecutions.getInstance();
|
||||
|
||||
// Poll every 60 seconds a list of upcoming executions
|
||||
this.mainTimer = setInterval(() => {
|
||||
this.getwaitingExecutions();
|
||||
}, 60000);
|
||||
|
||||
this.getwaitingExecutions();
|
||||
}
|
||||
|
||||
|
||||
async getwaitingExecutions() {
|
||||
Logger.debug('Wait tracker querying database for waiting executions');
|
||||
// Find all the executions which should be triggered in the next 70 seconds
|
||||
const findQuery: FindManyOptions<IExecutionFlattedDb> = {
|
||||
select: ['id', 'waitTill'],
|
||||
where: {
|
||||
waitTill: LessThanOrEqual(new Date(Date.now() + 70000)),
|
||||
},
|
||||
order: {
|
||||
waitTill: 'ASC',
|
||||
},
|
||||
};
|
||||
const dbType = await GenericHelpers.getConfigValue('database.type') as DatabaseType;
|
||||
if (dbType === 'sqlite') {
|
||||
// This is needed because of issue in TypeORM <> SQLite:
|
||||
// https://github.com/typeorm/typeorm/issues/2286
|
||||
(findQuery.where! as ObjectLiteral).waitTill = LessThanOrEqual(DateUtils.mixedDateToUtcDatetimeString(new Date(Date.now() + 70000)));
|
||||
}
|
||||
|
||||
const executions = await Db.collections.Execution!.find(findQuery);
|
||||
|
||||
if (executions.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const executionIds = executions.map(execution => execution.id.toString()).join(', ');
|
||||
Logger.debug(`Wait tracker found ${executions.length} executions. Setting timer for IDs: ${executionIds}`);
|
||||
|
||||
// Add timers for each waiting execution that they get started at the correct time
|
||||
for (const execution of executions) {
|
||||
const executionId = execution.id.toString();
|
||||
if (this.waitingExecutions[executionId] === undefined) {
|
||||
const triggerTime = execution.waitTill!.getTime() - new Date().getTime();
|
||||
this.waitingExecutions[executionId] = {
|
||||
executionId,
|
||||
timer: setTimeout(() => {
|
||||
this.startExecution(executionId);
|
||||
}, triggerTime),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async stopExecution(executionId: string): Promise<IExecutionsStopData> {
|
||||
if (this.waitingExecutions[executionId] !== undefined) {
|
||||
// The waiting execution was already sheduled to execute.
|
||||
// So stop timer and remove.
|
||||
clearTimeout(this.waitingExecutions[executionId].timer);
|
||||
delete this.waitingExecutions[executionId];
|
||||
}
|
||||
|
||||
// Also check in database
|
||||
const execution = await Db.collections.Execution!.findOne(executionId);
|
||||
|
||||
if (execution === undefined || !execution.waitTill) {
|
||||
throw new Error(`The execution ID "${executionId}" could not be found.`);
|
||||
}
|
||||
|
||||
const fullExecutionData = ResponseHelper.unflattenExecutionData(execution);
|
||||
|
||||
// Set in execution in DB as failed and remove waitTill time
|
||||
const error = new WorkflowOperationError('Workflow-Execution has been canceled!');
|
||||
|
||||
fullExecutionData.data.resultData.error = {
|
||||
...error,
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
};
|
||||
|
||||
fullExecutionData.stoppedAt = new Date();
|
||||
fullExecutionData.waitTill = undefined;
|
||||
|
||||
await Db.collections.Execution!.update(executionId, ResponseHelper.flattenExecutionData(fullExecutionData));
|
||||
|
||||
return {
|
||||
mode: fullExecutionData.mode,
|
||||
startedAt: new Date(fullExecutionData.startedAt),
|
||||
stoppedAt: fullExecutionData.stoppedAt ? new Date(fullExecutionData.stoppedAt) : undefined,
|
||||
finished: fullExecutionData.finished,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
startExecution(executionId: string) {
|
||||
Logger.debug(`Wait tracker resuming execution ${executionId}`, {executionId});
|
||||
delete this.waitingExecutions[executionId];
|
||||
|
||||
(async () => {
|
||||
// Get the data to execute
|
||||
const fullExecutionDataFlatted = await Db.collections.Execution!.findOne(executionId);
|
||||
|
||||
if (fullExecutionDataFlatted === undefined) {
|
||||
throw new Error(`The execution with the id "${executionId}" does not exist.`);
|
||||
}
|
||||
|
||||
const fullExecutionData = ResponseHelper.unflattenExecutionData(fullExecutionDataFlatted);
|
||||
|
||||
if (fullExecutionData.finished === true) {
|
||||
throw new Error('The execution did succeed and can so not be started again.');
|
||||
}
|
||||
|
||||
const data: IWorkflowExecutionDataProcess = {
|
||||
executionMode: fullExecutionData.mode,
|
||||
executionData: fullExecutionData.data,
|
||||
workflowData: fullExecutionData.workflowData,
|
||||
};
|
||||
|
||||
// Start the execution again
|
||||
const workflowRunner = new WorkflowRunner();
|
||||
await workflowRunner.run(data, false, false, executionId);
|
||||
})().catch((error) => {
|
||||
Logger.error(`There was a problem starting the waiting execution with id "${executionId}": "${error.message}"`, { executionId });
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let waitTrackerInstance: WaitTrackerClass | undefined;
|
||||
|
||||
export function WaitTracker(): WaitTrackerClass {
|
||||
if (waitTrackerInstance === undefined) {
|
||||
waitTrackerInstance = new WaitTrackerClass();
|
||||
}
|
||||
|
||||
return waitTrackerInstance;
|
||||
}
|
117
packages/cli/src/WaitingWebhooks.ts
Normal file
117
packages/cli/src/WaitingWebhooks.ts
Normal file
|
@ -0,0 +1,117 @@
|
|||
import {
|
||||
Db,
|
||||
IExecutionResponse,
|
||||
IResponseCallbackData,
|
||||
IWorkflowDb,
|
||||
NodeTypes,
|
||||
ResponseHelper,
|
||||
WebhookHelpers,
|
||||
WorkflowCredentials,
|
||||
WorkflowExecuteAdditionalData,
|
||||
} from '.';
|
||||
|
||||
import {
|
||||
INode,
|
||||
IRunExecutionData,
|
||||
NodeHelpers,
|
||||
WebhookHttpMethod,
|
||||
Workflow,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import * as express from 'express';
|
||||
import {
|
||||
LoggerProxy as Logger,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class WaitingWebhooks {
|
||||
|
||||
async executeWebhook(httpMethod: WebhookHttpMethod, fullPath: string, req: express.Request, res: express.Response): Promise<IResponseCallbackData> {
|
||||
Logger.debug(`Received waiting-webhoook "${httpMethod}" for path "${fullPath}"`);
|
||||
|
||||
// Reset request parameters
|
||||
req.params = {};
|
||||
|
||||
// Remove trailing slash
|
||||
if (fullPath.endsWith('/')) {
|
||||
fullPath = fullPath.slice(0, -1);
|
||||
}
|
||||
|
||||
const pathParts = fullPath.split('/');
|
||||
|
||||
const executionId = pathParts.shift();
|
||||
const path = pathParts.join('/');
|
||||
|
||||
const execution = await Db.collections.Execution?.findOne(executionId);
|
||||
|
||||
if (execution === undefined) {
|
||||
throw new ResponseHelper.ResponseError(`The execution "${executionId} does not exist.`, 404, 404);
|
||||
}
|
||||
|
||||
const fullExecutionData = ResponseHelper.unflattenExecutionData(execution);
|
||||
|
||||
if (fullExecutionData.finished === true || fullExecutionData.data.resultData.error) {
|
||||
throw new ResponseHelper.ResponseError(`The execution "${executionId} has finished already.`, 409, 409);
|
||||
}
|
||||
|
||||
return this.startExecution(httpMethod, path, fullExecutionData, req, res);
|
||||
}
|
||||
|
||||
|
||||
async startExecution(httpMethod: WebhookHttpMethod, path: string, fullExecutionData: IExecutionResponse, req: express.Request, res: express.Response): Promise<IResponseCallbackData> {
|
||||
const executionId = fullExecutionData.id;
|
||||
|
||||
if (fullExecutionData.finished === true) {
|
||||
throw new Error('The execution did succeed and can so not be started again.');
|
||||
}
|
||||
|
||||
const lastNodeExecuted = fullExecutionData!.data.resultData.lastNodeExecuted as string;
|
||||
|
||||
// Set the node as disabled so that the data does not get executed again as it would result
|
||||
// in starting the wait all over again
|
||||
fullExecutionData!.data.executionData!.nodeExecutionStack[0].node.disabled = true;
|
||||
|
||||
// Remove waitTill information else the execution would stop
|
||||
fullExecutionData!.data.waitTill = undefined;
|
||||
|
||||
// Remove the data of the node execution again else it will display the node as executed twice
|
||||
fullExecutionData!.data.resultData.runData[lastNodeExecuted].pop();
|
||||
|
||||
const workflowData = fullExecutionData.workflowData;
|
||||
|
||||
const nodeTypes = NodeTypes();
|
||||
const workflow = new Workflow({ id: workflowData.id!.toString(), name: workflowData.name, nodes: workflowData.nodes, connections: workflowData.connections, active: workflowData.active, nodeTypes, staticData: workflowData.staticData, settings: workflowData.settings });
|
||||
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase();
|
||||
|
||||
const webhookData = NodeHelpers.getNodeWebhooks(workflow, workflow.getNode(lastNodeExecuted) as INode, additionalData).filter((webhook) => {
|
||||
return (webhook.httpMethod === httpMethod && webhook.path === path && webhook.webhookDescription.restartWebhook === true);
|
||||
})[0];
|
||||
|
||||
if (webhookData === undefined) {
|
||||
// If no data got found it means that the execution can not be started via a webhook.
|
||||
// Return 404 because we do not want to give any data if the execution exists or not.
|
||||
const errorMessage = `The execution "${executionId}" with webhook suffix path "${path}" is not known.`;
|
||||
throw new ResponseHelper.ResponseError(errorMessage, 404, 404);
|
||||
}
|
||||
|
||||
const workflowStartNode = workflow.getNode(lastNodeExecuted);
|
||||
|
||||
if (workflowStartNode === null) {
|
||||
throw new ResponseHelper.ResponseError('Could not find node to process webhook.', 404, 404);
|
||||
}
|
||||
|
||||
const runExecutionData = fullExecutionData.data as IRunExecutionData;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const executionMode = 'webhook';
|
||||
WebhookHelpers.executeWebhook(workflow, webhookData, workflowData as IWorkflowDb, workflowStartNode, executionMode, undefined, runExecutionData, fullExecutionData.id, req, res, (error: Error | null, data: object) => {
|
||||
if (error !== null) {
|
||||
return reject(error);
|
||||
}
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -3,7 +3,6 @@ import { get } from 'lodash';
|
|||
|
||||
import {
|
||||
ActiveExecutions,
|
||||
ExternalHooks,
|
||||
GenericHelpers,
|
||||
IExecutionDb,
|
||||
IResponseCallbackData,
|
||||
|
@ -29,6 +28,7 @@ import {
|
|||
IRunExecutionData,
|
||||
IWebhookData,
|
||||
IWebhookResponseData,
|
||||
IWorkflowDataProxyAdditionalKeys,
|
||||
IWorkflowExecuteAdditionalData,
|
||||
LoggerProxy as Logger,
|
||||
NodeHelpers,
|
||||
|
@ -47,7 +47,7 @@ const activeExecutions = ActiveExecutions.getInstance();
|
|||
* @param {Workflow} workflow
|
||||
* @returns {IWebhookData[]}
|
||||
*/
|
||||
export function getWorkflowWebhooks(workflow: Workflow, additionalData: IWorkflowExecuteAdditionalData, destinationNode?: string): IWebhookData[] {
|
||||
export function getWorkflowWebhooks(workflow: Workflow, additionalData: IWorkflowExecuteAdditionalData, destinationNode?: string, ignoreRestartWehbooks = false): IWebhookData[] {
|
||||
// Check all the nodes in the workflow if they have webhooks
|
||||
|
||||
const returnData: IWebhookData[] = [];
|
||||
|
@ -65,7 +65,7 @@ export function getWorkflowWebhooks(workflow: Workflow, additionalData: IWorkflo
|
|||
// and no other ones
|
||||
continue;
|
||||
}
|
||||
returnData.push.apply(returnData, NodeHelpers.getNodeWebhooks(workflow, node, additionalData));
|
||||
returnData.push.apply(returnData, NodeHelpers.getNodeWebhooks(workflow, node, additionalData, ignoreRestartWehbooks));
|
||||
}
|
||||
|
||||
return returnData;
|
||||
|
@ -106,7 +106,7 @@ export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] {
|
|||
* @param {((error: Error | null, data: IResponseCallbackData) => void)} responseCallback
|
||||
* @returns {(Promise<string | undefined>)}
|
||||
*/
|
||||
export async function executeWebhook(workflow: Workflow, webhookData: IWebhookData, workflowData: IWorkflowDb, workflowStartNode: INode, executionMode: WorkflowExecuteMode, sessionId: string | undefined, req: express.Request, res: express.Response, responseCallback: (error: Error | null, data: IResponseCallbackData) => void): Promise<string | undefined> {
|
||||
export async function executeWebhook(workflow: Workflow, webhookData: IWebhookData, workflowData: IWorkflowDb, workflowStartNode: INode, executionMode: WorkflowExecuteMode, sessionId: string | undefined, runExecutionData: IRunExecutionData | undefined, executionId: string | undefined, req: express.Request, res: express.Response, responseCallback: (error: Error | null, data: IResponseCallbackData) => void): Promise<string | undefined> {
|
||||
// Get the nodeType to know which responseMode is set
|
||||
const nodeType = workflow.nodeTypes.getByName(workflowStartNode.type);
|
||||
if (nodeType === undefined) {
|
||||
|
@ -115,9 +115,13 @@ export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] {
|
|||
throw new ResponseHelper.ResponseError(errorMessage, 500, 500);
|
||||
}
|
||||
|
||||
const additionalKeys: IWorkflowDataProxyAdditionalKeys = {
|
||||
$executionId: executionId,
|
||||
};
|
||||
|
||||
// Get the responseMode
|
||||
const responseMode = workflow.expression.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseMode'], executionMode, 'onReceived');
|
||||
const responseCode = workflow.expression.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseCode'], executionMode, 200) as number;
|
||||
const responseMode = workflow.expression.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseMode'], executionMode, additionalKeys, 'onReceived');
|
||||
const responseCode = workflow.expression.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseCode'], executionMode, additionalKeys, 200) as number;
|
||||
|
||||
if (!['onReceived', 'lastNode'].includes(responseMode as string)) {
|
||||
// If the mode is not known we error. Is probably best like that instead of using
|
||||
|
@ -129,8 +133,7 @@ export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] {
|
|||
}
|
||||
|
||||
// Prepare everything that is needed to run the workflow
|
||||
const credentials = await WorkflowCredentials(workflowData.nodes);
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(credentials);
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase();
|
||||
|
||||
// Add the Response and Request so that this data can be accessed in the node
|
||||
additionalData.httpRequest = req;
|
||||
|
@ -175,8 +178,12 @@ export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] {
|
|||
// Save static data if it changed
|
||||
await WorkflowHelpers.saveStaticData(workflow);
|
||||
|
||||
const additionalKeys: IWorkflowDataProxyAdditionalKeys = {
|
||||
$executionId: executionId,
|
||||
};
|
||||
|
||||
if (webhookData.webhookDescription['responseHeaders'] !== undefined) {
|
||||
const responseHeaders = workflow.expression.getComplexParameterValue(workflowStartNode, webhookData.webhookDescription['responseHeaders'], executionMode, undefined) as {
|
||||
const responseHeaders = workflow.expression.getComplexParameterValue(workflowStartNode, webhookData.webhookDescription['responseHeaders'], executionMode, additionalKeys, undefined) as {
|
||||
entries?: Array<{
|
||||
name: string;
|
||||
value: string;
|
||||
|
@ -257,7 +264,7 @@ export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] {
|
|||
}
|
||||
);
|
||||
|
||||
const runExecutionData: IRunExecutionData = {
|
||||
runExecutionData = runExecutionData || {
|
||||
startData: {
|
||||
},
|
||||
resultData: {
|
||||
|
@ -268,7 +275,13 @@ export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] {
|
|||
nodeExecutionStack,
|
||||
waitingExecution: {},
|
||||
},
|
||||
};
|
||||
} as IRunExecutionData;
|
||||
|
||||
if (executionId !== undefined) {
|
||||
// Set the data the webhook node did return on the waiting node if executionId
|
||||
// already exists as it means that we are restarting an existing execution.
|
||||
runExecutionData.executionData!.nodeExecutionStack[0].data.main = webhookResultData.workflowData;
|
||||
}
|
||||
|
||||
if (Object.keys(runExecutionDataMerge).length !== 0) {
|
||||
// If data to merge got defined add it to the execution data
|
||||
|
@ -276,7 +289,6 @@ export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] {
|
|||
}
|
||||
|
||||
const runData: IWorkflowExecutionDataProcess = {
|
||||
credentials,
|
||||
executionMode,
|
||||
executionData: runExecutionData,
|
||||
sessionId,
|
||||
|
@ -285,7 +297,7 @@ export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] {
|
|||
|
||||
// Start now to run the workflow
|
||||
const workflowRunner = new WorkflowRunner();
|
||||
const executionId = await workflowRunner.run(runData, true, !didSendResponse);
|
||||
executionId = await workflowRunner.run(runData, true, !didSendResponse, executionId);
|
||||
|
||||
Logger.verbose(`Started execution of workflow "${workflow.name}" from webhook with execution ID ${executionId}`, { executionId });
|
||||
|
||||
|
@ -332,7 +344,11 @@ export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] {
|
|||
return data;
|
||||
}
|
||||
|
||||
const responseData = workflow.expression.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseData'], executionMode, 'firstEntryJson');
|
||||
const additionalKeys: IWorkflowDataProxyAdditionalKeys = {
|
||||
$executionId: executionId,
|
||||
};
|
||||
|
||||
const responseData = workflow.expression.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseData'], executionMode, additionalKeys, 'firstEntryJson');
|
||||
|
||||
if (didSendResponse === false) {
|
||||
let data: IDataObject | IDataObject[];
|
||||
|
@ -347,13 +363,13 @@ export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] {
|
|||
|
||||
data = returnData.data!.main[0]![0].json;
|
||||
|
||||
const responsePropertyName = workflow.expression.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responsePropertyName'], executionMode, undefined);
|
||||
const responsePropertyName = workflow.expression.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responsePropertyName'], executionMode, additionalKeys, undefined);
|
||||
|
||||
if (responsePropertyName !== undefined) {
|
||||
data = get(data, responsePropertyName as string) as IDataObject;
|
||||
}
|
||||
|
||||
const responseContentType = workflow.expression.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseContentType'], executionMode, undefined);
|
||||
const responseContentType = workflow.expression.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseContentType'], executionMode, additionalKeys, undefined);
|
||||
|
||||
if (responseContentType !== undefined) {
|
||||
// Send the webhook response manually to be able to set the content-type
|
||||
|
@ -386,7 +402,7 @@ export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] {
|
|||
didSendResponse = true;
|
||||
}
|
||||
|
||||
const responseBinaryPropertyName = workflow.expression.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseBinaryPropertyName'], executionMode, 'data');
|
||||
const responseBinaryPropertyName = workflow.expression.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseBinaryPropertyName'], executionMode, additionalKeys, 'data');
|
||||
|
||||
if (responseBinaryPropertyName === undefined && didSendResponse === false) {
|
||||
responseCallback(new Error('No "responseBinaryPropertyName" is set.'), {});
|
||||
|
|
|
@ -26,6 +26,11 @@ import * as config from '../config';
|
|||
import * as parseUrl from 'parseurl';
|
||||
|
||||
export function registerProductionWebhooks() {
|
||||
|
||||
// ----------------------------------------
|
||||
// Regular Webhooks
|
||||
// ----------------------------------------
|
||||
|
||||
// HEAD webhook requests
|
||||
this.app.head(`/${this.endpointWebhook}/*`, async (req: express.Request, res: express.Response) => {
|
||||
// Cut away the "/webhook/" to get the registred part of the url
|
||||
|
|
|
@ -256,7 +256,7 @@ export function hookFunctionsPreExecute(parentProcessMode?: string): IWorkflowEx
|
|||
if (execution === undefined) {
|
||||
// Something went badly wrong if this happens.
|
||||
// This check is here mostly to make typescript happy.
|
||||
return undefined;
|
||||
return;
|
||||
}
|
||||
const fullExecutionData: IExecutionResponse = ResponseHelper.unflattenExecutionData(execution);
|
||||
|
||||
|
@ -267,11 +267,9 @@ export function hookFunctionsPreExecute(parentProcessMode?: string): IWorkflowEx
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
if (fullExecutionData.data === undefined) {
|
||||
fullExecutionData.data = {
|
||||
startData: {
|
||||
},
|
||||
startData: {},
|
||||
resultData: {
|
||||
runData: {},
|
||||
},
|
||||
|
@ -351,7 +349,7 @@ function hookFunctionsSave(parentProcessMode?: string): IWorkflowExecuteHooks {
|
|||
saveManualExecutions = this.workflowData.settings.saveManualExecutions as boolean;
|
||||
}
|
||||
|
||||
if (isManualMode && saveManualExecutions === false) {
|
||||
if (isManualMode && saveManualExecutions === false && !fullRunData.waitTill) {
|
||||
// Data is always saved, so we remove from database
|
||||
await Db.collections.Execution!.delete(this.executionId);
|
||||
return;
|
||||
|
@ -369,12 +367,14 @@ function hookFunctionsSave(parentProcessMode?: string): IWorkflowExecuteHooks {
|
|||
if (workflowDidSucceed === true && saveDataSuccessExecution === 'none' ||
|
||||
workflowDidSucceed === false && saveDataErrorExecution === 'none'
|
||||
) {
|
||||
if (!isManualMode) {
|
||||
executeErrorWorkflow(this.workflowData, fullRunData, this.mode, undefined, this.retryOf);
|
||||
if (!fullRunData.waitTill) {
|
||||
if (!isManualMode) {
|
||||
executeErrorWorkflow(this.workflowData, fullRunData, this.mode, undefined, this.retryOf);
|
||||
}
|
||||
// Data is always saved, so we remove from database
|
||||
await Db.collections.Execution!.delete(this.executionId);
|
||||
return;
|
||||
}
|
||||
// Data is always saved, so we remove from database
|
||||
await Db.collections.Execution!.delete(this.executionId);
|
||||
return;
|
||||
}
|
||||
|
||||
const fullExecutionData: IExecutionDb = {
|
||||
|
@ -384,6 +384,7 @@ function hookFunctionsSave(parentProcessMode?: string): IWorkflowExecuteHooks {
|
|||
startedAt: fullRunData.startedAt,
|
||||
stoppedAt: fullRunData.stoppedAt,
|
||||
workflowData: this.workflowData,
|
||||
waitTill: fullRunData.waitTill,
|
||||
};
|
||||
|
||||
if (this.retryOf !== undefined) {
|
||||
|
@ -469,6 +470,7 @@ function hookFunctionsSaveWorker(): IWorkflowExecuteHooks {
|
|||
startedAt: fullRunData.startedAt,
|
||||
stoppedAt: fullRunData.stoppedAt,
|
||||
workflowData: this.workflowData,
|
||||
waitTill: fullRunData.data.waitTill,
|
||||
};
|
||||
|
||||
if (this.retryOf !== undefined) {
|
||||
|
@ -545,12 +547,7 @@ export async function getRunData(workflowData: IWorkflowBase, inputData?: INodeE
|
|||
},
|
||||
};
|
||||
|
||||
// Get the needed credentials for the current workflow as they will differ to the ones of the
|
||||
// calling workflow.
|
||||
const credentials = await WorkflowCredentials(workflowData!.nodes);
|
||||
|
||||
const runData: IWorkflowExecutionDataProcess = {
|
||||
credentials,
|
||||
executionMode: mode,
|
||||
executionData: runExecutionData,
|
||||
// @ts-ignore
|
||||
|
@ -618,13 +615,9 @@ export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additi
|
|||
|
||||
let data;
|
||||
try {
|
||||
// Get the needed credentials for the current workflow as they will differ to the ones of the
|
||||
// calling workflow.
|
||||
const credentials = await WorkflowCredentials(workflowData!.nodes);
|
||||
|
||||
// Create new additionalData to have different workflow loaded and to call
|
||||
// different webooks
|
||||
const additionalDataIntegrated = await getBase(credentials);
|
||||
const additionalDataIntegrated = await getBase();
|
||||
additionalDataIntegrated.hooks = getWorkflowHooksIntegrated(runData.executionMode, executionId, workflowData!, { parentProcessMode: additionalData.hooks!.mode });
|
||||
// Make sure we pass on the original executeWorkflow function we received
|
||||
// This one already contains changes to talk to parent process
|
||||
|
@ -735,11 +728,12 @@ export function sendMessageToUI(source: string, message: any) { // tslint:disabl
|
|||
* @param {INodeParameters} currentNodeParameters
|
||||
* @returns {Promise<IWorkflowExecuteAdditionalData>}
|
||||
*/
|
||||
export async function getBase(credentials: IWorkflowCredentials, currentNodeParameters?: INodeParameters, executionTimeoutTimestamp?: number): Promise<IWorkflowExecuteAdditionalData> {
|
||||
export async function getBase(currentNodeParameters?: INodeParameters, executionTimeoutTimestamp?: number): Promise<IWorkflowExecuteAdditionalData> {
|
||||
const urlBaseWebhook = WebhookHelpers.getWebhookBaseUrl();
|
||||
|
||||
const timezone = config.get('generic.timezone') as string;
|
||||
const webhookBaseUrl = urlBaseWebhook + config.get('endpoints.webhook') as string;
|
||||
const webhookWaitingBaseUrl = urlBaseWebhook + config.get('endpoints.webhookWaiting') as string;
|
||||
const webhookTestBaseUrl = urlBaseWebhook + config.get('endpoints.webhookTest') as string;
|
||||
|
||||
const encryptionKey = await UserSettings.getEncryptionKey();
|
||||
|
@ -748,13 +742,13 @@ export async function getBase(credentials: IWorkflowCredentials, currentNodePara
|
|||
}
|
||||
|
||||
return {
|
||||
credentials,
|
||||
credentialsHelper: new CredentialsHelper(credentials, encryptionKey),
|
||||
credentialsHelper: new CredentialsHelper(encryptionKey),
|
||||
encryptionKey,
|
||||
executeWorkflow,
|
||||
restApiUrl: urlBaseWebhook + config.get('endpoints.rest') as string,
|
||||
timezone,
|
||||
webhookBaseUrl,
|
||||
webhookWaitingBaseUrl,
|
||||
webhookTestBaseUrl,
|
||||
currentNodeParameters,
|
||||
executionTimeoutTimestamp,
|
||||
|
|
|
@ -144,10 +144,7 @@ export async function executeErrorWorkflow(workflowId: string, workflowErrorData
|
|||
},
|
||||
};
|
||||
|
||||
const credentials = await WorkflowCredentials(workflowData.nodes);
|
||||
|
||||
const runData: IWorkflowExecutionDataProcess = {
|
||||
credentials,
|
||||
executionMode,
|
||||
executionData: runExecutionData,
|
||||
workflowData,
|
||||
|
|
|
@ -123,19 +123,18 @@ export class WorkflowRunner {
|
|||
* @returns {Promise<string>}
|
||||
* @memberof WorkflowRunner
|
||||
*/
|
||||
async run(data: IWorkflowExecutionDataProcess, loadStaticData?: boolean, realtime?: boolean): Promise<string> {
|
||||
async run(data: IWorkflowExecutionDataProcess, loadStaticData?: boolean, realtime?: boolean, executionId?: string): Promise<string> {
|
||||
const executionsProcess = config.get('executions.process') as string;
|
||||
const executionsMode = config.get('executions.mode') as string;
|
||||
|
||||
let executionId: string;
|
||||
if (executionsMode === 'queue' && data.executionMode !== 'manual') {
|
||||
// Do not run "manual" executions in bull because sending events to the
|
||||
// frontend would not be possible
|
||||
executionId = await this.runBull(data, loadStaticData, realtime);
|
||||
executionId = await this.runBull(data, loadStaticData, realtime, executionId);
|
||||
} else if (executionsProcess === 'main') {
|
||||
executionId = await this.runMainProcess(data, loadStaticData);
|
||||
executionId = await this.runMainProcess(data, loadStaticData, executionId);
|
||||
} else {
|
||||
executionId = await this.runSubprocess(data, loadStaticData);
|
||||
executionId = await this.runSubprocess(data, loadStaticData, executionId);
|
||||
}
|
||||
|
||||
const externalHooks = ExternalHooks();
|
||||
|
@ -162,7 +161,7 @@ export class WorkflowRunner {
|
|||
* @returns {Promise<string>}
|
||||
* @memberof WorkflowRunner
|
||||
*/
|
||||
async runMainProcess(data: IWorkflowExecutionDataProcess, loadStaticData?: boolean): Promise<string> {
|
||||
async runMainProcess(data: IWorkflowExecutionDataProcess, loadStaticData?: boolean, restartExecutionId?: string): Promise<string> {
|
||||
if (loadStaticData === true && data.workflowData.id) {
|
||||
data.workflowData.staticData = await WorkflowHelpers.getStaticDataById(data.workflowData.id as string);
|
||||
}
|
||||
|
@ -183,10 +182,13 @@ export class WorkflowRunner {
|
|||
}
|
||||
|
||||
const workflow = new Workflow({ id: data.workflowData.id as string | undefined, name: data.workflowData.name, nodes: data.workflowData!.nodes, connections: data.workflowData!.connections, active: data.workflowData!.active, nodeTypes, staticData: data.workflowData!.staticData });
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(data.credentials, undefined, workflowTimeout <= 0 ? undefined : Date.now() + workflowTimeout * 1000);
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(undefined, workflowTimeout <= 0 ? undefined : Date.now() + workflowTimeout * 1000);
|
||||
|
||||
// Register the active execution
|
||||
const executionId = await this.activeExecutions.add(data, undefined);
|
||||
const executionId = await this.activeExecutions.add(data, undefined, restartExecutionId) as string;
|
||||
additionalData.executionId = executionId;
|
||||
|
||||
Logger.verbose(`Execution for workflow ${data.workflowData.name} was assigned id ${executionId}`, {executionId});
|
||||
let workflowExecution: PCancelable<IRun>;
|
||||
|
||||
try {
|
||||
|
@ -240,12 +242,12 @@ export class WorkflowRunner {
|
|||
return executionId;
|
||||
}
|
||||
|
||||
async runBull(data: IWorkflowExecutionDataProcess, loadStaticData?: boolean, realtime?: boolean): Promise<string> {
|
||||
async runBull(data: IWorkflowExecutionDataProcess, loadStaticData?: boolean, realtime?: boolean, restartExecutionId?: string): Promise<string> {
|
||||
|
||||
// TODO: If "loadStaticData" is set to true it has to load data new on worker
|
||||
|
||||
// Register the active execution
|
||||
const executionId = await this.activeExecutions.add(data, undefined);
|
||||
const executionId = await this.activeExecutions.add(data, undefined, restartExecutionId);
|
||||
|
||||
const jobData: IBullJobData = {
|
||||
executionId,
|
||||
|
@ -412,7 +414,7 @@ export class WorkflowRunner {
|
|||
* @returns {Promise<string>}
|
||||
* @memberof WorkflowRunner
|
||||
*/
|
||||
async runSubprocess(data: IWorkflowExecutionDataProcess, loadStaticData?: boolean): Promise<string> {
|
||||
async runSubprocess(data: IWorkflowExecutionDataProcess, loadStaticData?: boolean, restartExecutionId?: string): Promise<string> {
|
||||
let startedAt = new Date();
|
||||
const subprocess = fork(pathJoin(__dirname, 'WorkflowRunnerProcess.js'));
|
||||
|
||||
|
@ -421,45 +423,16 @@ export class WorkflowRunner {
|
|||
}
|
||||
|
||||
// Register the active execution
|
||||
const executionId = await this.activeExecutions.add(data, subprocess);
|
||||
const executionId = await this.activeExecutions.add(data, subprocess, restartExecutionId);
|
||||
|
||||
// Check if workflow contains a "executeWorkflow" Node as in this
|
||||
// case we can not know which nodeTypes and credentialTypes will
|
||||
// be needed and so have to load all of them in the workflowRunnerProcess
|
||||
let loadAllNodeTypes = false;
|
||||
for (const node of data.workflowData.nodes) {
|
||||
if (node.type === 'n8n-nodes-base.executeWorkflow') {
|
||||
loadAllNodeTypes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let nodeTypeData: ITransferNodeTypes;
|
||||
let credentialTypeData: ICredentialsTypeData;
|
||||
let credentialsOverwrites = this.credentialsOverwrites;
|
||||
|
||||
if (loadAllNodeTypes === true) {
|
||||
// Supply all nodeTypes and credentialTypes
|
||||
nodeTypeData = WorkflowHelpers.getAllNodeTypeData();
|
||||
const credentialTypes = CredentialTypes();
|
||||
credentialTypeData = credentialTypes.credentialTypes;
|
||||
} else {
|
||||
// Supply only nodeTypes, credentialTypes and overwrites that the workflow needs
|
||||
nodeTypeData = WorkflowHelpers.getNodeTypeData(data.workflowData.nodes);
|
||||
credentialTypeData = WorkflowHelpers.getCredentialsData(data.credentials);
|
||||
|
||||
credentialsOverwrites = {};
|
||||
for (const credentialName of Object.keys(credentialTypeData)) {
|
||||
if (this.credentialsOverwrites[credentialName] !== undefined) {
|
||||
credentialsOverwrites[credentialName] = this.credentialsOverwrites[credentialName];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Supply all nodeTypes and credentialTypes
|
||||
const nodeTypeData = WorkflowHelpers.getAllNodeTypeData() as ITransferNodeTypes;
|
||||
const credentialTypes = CredentialTypes();
|
||||
|
||||
(data as unknown as IWorkflowExecutionDataProcessWithExecution).executionId = executionId;
|
||||
(data as unknown as IWorkflowExecutionDataProcessWithExecution).nodeTypeData = nodeTypeData;
|
||||
(data as unknown as IWorkflowExecutionDataProcessWithExecution).credentialsOverwrite = credentialsOverwrites;
|
||||
(data as unknown as IWorkflowExecutionDataProcessWithExecution).credentialsTypeData = credentialTypeData; // TODO: Still needs correct value
|
||||
(data as unknown as IWorkflowExecutionDataProcessWithExecution).credentialsOverwrite = this.credentialsOverwrites;
|
||||
(data as unknown as IWorkflowExecutionDataProcessWithExecution).credentialsTypeData = credentialTypes.credentialTypes;
|
||||
|
||||
const workflowHooks = WorkflowExecuteAdditionalData.getWorkflowHooksMain(data, executionId);
|
||||
|
||||
|
|
|
@ -111,9 +111,22 @@ export class WorkflowRunnerProcess {
|
|||
const externalHooks = ExternalHooks();
|
||||
await externalHooks.init();
|
||||
|
||||
// This code has been split into 3 ifs just to make it easier to understand
|
||||
// Credentials should now be loaded from database.
|
||||
// We check if any node uses credentials. If it does, then
|
||||
// init database.
|
||||
let shouldInitializaDb = false;
|
||||
inputData.workflowData.nodes.map(node => {
|
||||
if (Object.keys(node.credentials === undefined ? {} : node.credentials).length > 0) {
|
||||
shouldInitializaDb = true;
|
||||
}
|
||||
});
|
||||
|
||||
// This code has been split into 4 ifs just to make it easier to understand
|
||||
// Can be made smaller but in the end it will make it impossible to read.
|
||||
if (inputData.workflowData.settings !== undefined && inputData.workflowData.settings.saveExecutionProgress === true) {
|
||||
if (shouldInitializaDb) {
|
||||
// initialize db as we need to load credentials
|
||||
await Db.init();
|
||||
} else if (inputData.workflowData.settings !== undefined && inputData.workflowData.settings.saveExecutionProgress === true) {
|
||||
// Workflow settings specifying it should save
|
||||
await Db.init();
|
||||
} else if (inputData.workflowData.settings !== undefined && inputData.workflowData.settings.saveExecutionProgress !== false && config.get('executions.saveExecutionProgress') as boolean) {
|
||||
|
@ -135,8 +148,9 @@ export class WorkflowRunnerProcess {
|
|||
}
|
||||
|
||||
this.workflow = new Workflow({ id: this.data.workflowData.id as string | undefined, name: this.data.workflowData.name, nodes: this.data.workflowData!.nodes, connections: this.data.workflowData!.connections, active: this.data.workflowData!.active, nodeTypes, staticData: this.data.workflowData!.staticData, settings: this.data.workflowData!.settings });
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(this.data.credentials, undefined, workflowTimeout <= 0 ? undefined : Date.now() + workflowTimeout * 1000);
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(undefined, workflowTimeout <= 0 ? undefined : Date.now() + workflowTimeout * 1000);
|
||||
additionalData.hooks = this.getProcessForwardHooks();
|
||||
additionalData.executionId = inputData.executionId;
|
||||
|
||||
additionalData.sendMessageToUI = async (source: string, message: any) => { // tslint:disable-line:no-any
|
||||
if (workflowRunner.data!.executionMode !== 'manual') {
|
||||
|
|
|
@ -53,4 +53,8 @@ export class ExecutionEntity implements IExecutionFlattedDb {
|
|||
@Index()
|
||||
@Column({ nullable: true })
|
||||
workflowId: string;
|
||||
|
||||
@Index()
|
||||
@Column({ type: resolveDataType('datetime') as ColumnOptions['type'], nullable: true })
|
||||
waitTill: Date;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||
import * as config from '../../../../config';
|
||||
|
||||
export class AddWaitColumnId1626183952959 implements MigrationInterface {
|
||||
name = 'AddWaitColumnId1626183952959';
|
||||
|
||||
async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const tablePrefix = config.get('database.tablePrefix');
|
||||
|
||||
await queryRunner.query('ALTER TABLE `' + tablePrefix + 'execution_entity` ADD `waitTill` DATETIME NULL');
|
||||
await queryRunner.query('CREATE INDEX `IDX_' + tablePrefix + 'ca4a71b47f28ac6ea88293a8e2` ON `' + tablePrefix + 'execution_entity` (`waitTill`)');
|
||||
}
|
||||
|
||||
async down(queryRunner: QueryRunner): Promise<void> {
|
||||
const tablePrefix = config.get('database.tablePrefix');
|
||||
|
||||
await queryRunner.query(
|
||||
'DROP INDEX `IDX_' + tablePrefix + 'ca4a71b47f28ac6ea88293a8e2` ON `' + tablePrefix + 'execution_entity`'
|
||||
);
|
||||
await queryRunner.query('ALTER TABLE `' + tablePrefix + 'execution_entity` DROP COLUMN `waitTill`');
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import { ChangeCredentialDataSize1620729500000 } from './1620729500000-ChangeCre
|
|||
import { CreateTagEntity1617268711084 } from './1617268711084-CreateTagEntity';
|
||||
import { UniqueWorkflowNames1620826335440 } from './1620826335440-UniqueWorkflowNames';
|
||||
import { CertifyCorrectCollation1623936588000 } from './1623936588000-CertifyCorrectCollation';
|
||||
import { AddWaitColumnId1626183952959 } from './1626183952959-AddWaitColumn';
|
||||
|
||||
export const mysqlMigrations = [
|
||||
InitialMigration1588157391238,
|
||||
|
@ -20,4 +21,5 @@ export const mysqlMigrations = [
|
|||
CreateTagEntity1617268711084,
|
||||
UniqueWorkflowNames1620826335440,
|
||||
CertifyCorrectCollation1623936588000,
|
||||
AddWaitColumnId1626183952959,
|
||||
];
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||
import * as config from '../../../../config';
|
||||
|
||||
export class AddwaitTill1626176912946 implements MigrationInterface {
|
||||
name = 'AddwaitTill1626176912946';
|
||||
|
||||
async up(queryRunner: QueryRunner): Promise<void> {
|
||||
let tablePrefix = config.get('database.tablePrefix');
|
||||
const tablePrefixPure = tablePrefix;
|
||||
const schema = config.get('database.postgresdb.schema');
|
||||
if (schema) {
|
||||
tablePrefix = schema + '.' + tablePrefix;
|
||||
}
|
||||
|
||||
await queryRunner.query(`ALTER TABLE ${tablePrefix}execution_entity ADD "waitTill" TIMESTAMP`);
|
||||
await queryRunner.query(`CREATE INDEX IF NOT EXISTS IDX_${tablePrefixPure}ca4a71b47f28ac6ea88293a8e2 ON ${tablePrefix}execution_entity ("waitTill")`);
|
||||
}
|
||||
|
||||
async down(queryRunner: QueryRunner): Promise<void> {
|
||||
let tablePrefix = config.get('database.tablePrefix');
|
||||
const tablePrefixPure = tablePrefix;
|
||||
const schema = config.get('database.postgresdb.schema');
|
||||
if (schema) {
|
||||
tablePrefix = schema + '.' + tablePrefix;
|
||||
}
|
||||
|
||||
await queryRunner.query(`DROP INDEX IDX_${tablePrefixPure}ca4a71b47f28ac6ea88293a8e2`);
|
||||
await queryRunner.query(`ALTER TABLE ${tablePrefix}webhook_entity DROP COLUMN "waitTill"`);
|
||||
}
|
||||
|
||||
}
|
|
@ -5,6 +5,7 @@ import { AddWebhookId1611144599516 } from './1611144599516-AddWebhookId';
|
|||
import { MakeStoppedAtNullable1607431743768 } from './1607431743768-MakeStoppedAtNullable';
|
||||
import { CreateTagEntity1617270242566 } from './1617270242566-CreateTagEntity';
|
||||
import { UniqueWorkflowNames1620824779533 } from './1620824779533-UniqueWorkflowNames';
|
||||
import { AddwaitTill1626176912946 } from './1626176912946-AddwaitTill';
|
||||
|
||||
export const postgresMigrations = [
|
||||
InitialMigration1587669153312,
|
||||
|
@ -14,4 +15,5 @@ export const postgresMigrations = [
|
|||
MakeStoppedAtNullable1607431743768,
|
||||
CreateTagEntity1617270242566,
|
||||
UniqueWorkflowNames1620824779533,
|
||||
AddwaitTill1626176912946,
|
||||
];
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
import * as config from '../../../../config';
|
||||
|
||||
export class AddWaitColumn1621707690587 implements MigrationInterface {
|
||||
name = 'AddWaitColumn1621707690587';
|
||||
|
||||
async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const tablePrefix = config.get('database.tablePrefix');
|
||||
|
||||
await queryRunner.query(`DROP TABLE IF EXISTS "${tablePrefix}temporary_execution_entity"`);
|
||||
await queryRunner.query(`CREATE TABLE "${tablePrefix}temporary_execution_entity" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "data" text NOT NULL, "finished" boolean NOT NULL, "mode" varchar NOT NULL, "retryOf" varchar, "retrySuccessId" varchar, "startedAt" datetime NOT NULL, "stoppedAt" datetime, "workflowData" text NOT NULL, "workflowId" varchar, "waitTill" DATETIME)`, undefined);
|
||||
await queryRunner.query(`INSERT INTO "${tablePrefix}temporary_execution_entity"("id", "data", "finished", "mode", "retryOf", "retrySuccessId", "startedAt", "stoppedAt", "workflowData", "workflowId") SELECT "id", "data", "finished", "mode", "retryOf", "retrySuccessId", "startedAt", "stoppedAt", "workflowData", "workflowId" FROM "${tablePrefix}execution_entity"`);
|
||||
await queryRunner.query(`DROP TABLE "${tablePrefix}execution_entity"`);
|
||||
await queryRunner.query(`ALTER TABLE "${tablePrefix}temporary_execution_entity" RENAME TO "${tablePrefix}execution_entity"`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_${tablePrefix}cefb067df2402f6aed0638a6c1" ON "${tablePrefix}execution_entity" ("stoppedAt")`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_${tablePrefix}ca4a71b47f28ac6ea88293a8e2" ON "${tablePrefix}execution_entity" ("waitTill")`);
|
||||
await queryRunner.query(`VACUUM;`);
|
||||
}
|
||||
|
||||
async down(queryRunner: QueryRunner): Promise<void> {
|
||||
const tablePrefix = config.get('database.tablePrefix');
|
||||
|
||||
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "${tablePrefix}temporary_execution_entity" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "data" text NOT NULL, "finished" boolean NOT NULL, "mode" varchar NOT NULL, "retryOf" varchar, "retrySuccessId" varchar, "startedAt" datetime NOT NULL, "stoppedAt" datetime, "workflowData" text NOT NULL, "workflowId" varchar)`, undefined);
|
||||
await queryRunner.query(`INSERT INTO "${tablePrefix}temporary_execution_entity"("id", "data", "finished", "mode", "retryOf", "retrySuccessId", "startedAt", "stoppedAt", "workflowData", "workflowId") SELECT "id", "data", "finished", "mode", "retryOf", "retrySuccessId", "startedAt", "stoppedAt", "workflowData", "workflowId" FROM "${tablePrefix}execution_entity"`);
|
||||
await queryRunner.query(`DROP TABLE "${tablePrefix}execution_entity"`);
|
||||
await queryRunner.query(`ALTER TABLE "${tablePrefix}temporary_execution_entity" RENAME TO "${tablePrefix}execution_entity"`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_${tablePrefix}cefb067df2402f6aed0638a6c1" ON "${tablePrefix}execution_entity" ("stoppedAt")`);
|
||||
await queryRunner.query(`VACUUM;`);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -5,6 +5,7 @@ import { AddWebhookId1611071044839 } from './1611071044839-AddWebhookId';
|
|||
import { MakeStoppedAtNullable1607431743769 } from './1607431743769-MakeStoppedAtNullable';
|
||||
import { CreateTagEntity1617213344594 } from './1617213344594-CreateTagEntity';
|
||||
import { UniqueWorkflowNames1620821879465 } from './1620821879465-UniqueWorkflowNames';
|
||||
import { AddWaitColumn1621707690587 } from './1621707690587-AddWaitColumn';
|
||||
|
||||
export const sqliteMigrations = [
|
||||
InitialMigration1588102412422,
|
||||
|
@ -14,4 +15,5 @@ export const sqliteMigrations = [
|
|||
MakeStoppedAtNullable1607431743769,
|
||||
CreateTagEntity1617213344594,
|
||||
UniqueWorkflowNames1620821879465,
|
||||
AddWaitColumn1621707690587,
|
||||
];
|
||||
|
|
|
@ -5,6 +5,8 @@ export * from './ExternalHooks';
|
|||
export * from './Interfaces';
|
||||
export * from './LoadNodesAndCredentials';
|
||||
export * from './NodeTypes';
|
||||
export * from './WaitTracker';
|
||||
export * from './WaitingWebhooks';
|
||||
export * from './WorkflowCredentials';
|
||||
export * from './WorkflowRunner';
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-core",
|
||||
"version": "0.79.0",
|
||||
"version": "0.80.0",
|
||||
"description": "Core functionality of n8n",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -50,7 +50,7 @@
|
|||
"file-type": "^14.6.2",
|
||||
"lodash.get": "^4.4.2",
|
||||
"mime-types": "^2.1.27",
|
||||
"n8n-workflow": "~0.64.0",
|
||||
"n8n-workflow": "~0.65.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"p-cancelable": "^2.0.0",
|
||||
"request": "^2.88.2",
|
||||
|
|
|
@ -5,4 +5,6 @@ export const EXTENSIONS_SUBDIRECTORY = 'custom';
|
|||
export const USER_FOLDER_ENV_OVERWRITE = 'N8N_USER_FOLDER';
|
||||
export const USER_SETTINGS_FILE_NAME = 'config';
|
||||
export const USER_SETTINGS_SUBFOLDER = '.n8n';
|
||||
export const PLACEHOLDER_EMPTY_EXECUTION_ID = '__UNKOWN__';
|
||||
export const TUNNEL_SUBDOMAIN_ENV = 'N8N_TUNNEL_SUBDOMAIN';
|
||||
export const WAIT_TIME_UNLIMITED = '3000-01-01T00:00:00.000Z';
|
||||
|
|
|
@ -34,9 +34,10 @@ export interface IProcessMessage {
|
|||
export interface IExecuteFunctions extends IExecuteFunctionsBase {
|
||||
helpers: {
|
||||
prepareBinaryData(binaryData: Buffer, filePath?: string, mimeType?: string): Promise<IBinaryData>;
|
||||
request: requestPromise.RequestPromiseAPI,
|
||||
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any>, // tslint:disable-line:no-any
|
||||
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any>, // tslint:disable-line:no-any
|
||||
getBinaryDataBuffer(itemIndex: number, propertyName: string): Promise<Buffer>;
|
||||
request: requestPromise.RequestPromiseAPI;
|
||||
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any>; // tslint:disable-line:no-any
|
||||
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any>; // tslint:disable-line:no-any
|
||||
returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[];
|
||||
};
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
ILoadOptionsFunctions,
|
||||
IResponseError,
|
||||
IWorkflowSettings,
|
||||
PLACEHOLDER_EMPTY_EXECUTION_ID,
|
||||
} from './';
|
||||
|
||||
import {
|
||||
|
@ -28,6 +29,7 @@ import {
|
|||
IWebhookData,
|
||||
IWebhookDescription,
|
||||
IWebhookFunctions,
|
||||
IWorkflowDataProxyAdditionalKeys,
|
||||
IWorkflowDataProxyData,
|
||||
IWorkflowExecuteAdditionalData,
|
||||
IWorkflowMetadata,
|
||||
|
@ -59,6 +61,21 @@ const requestPromiseWithDefaults = requestPromise.defaults({
|
|||
timeout: 300000, // 5 minutes
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns binary data buffer for given item index and property name.
|
||||
*
|
||||
* @export
|
||||
* @param {ITaskDataConnections} inputData
|
||||
* @param {number} itemIndex
|
||||
* @param {string} propertyName
|
||||
* @param {number} inputIndex
|
||||
* @returns {Promise<Buffer>}
|
||||
*/
|
||||
export async function getBinaryDataBuffer(inputData: ITaskDataConnections, itemIndex: number, propertyName: string, inputIndex: number): Promise<Buffer> {
|
||||
const binaryData = inputData['main']![inputIndex]![itemIndex]!.binary![propertyName]!;
|
||||
return Buffer.from(binaryData.data, BINARY_ENCODING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a buffer and converts it into the format n8n uses. It encodes the binary data as
|
||||
* base64 and adds metadata.
|
||||
|
@ -140,8 +157,8 @@ export async function prepareBinaryData(binaryData: Buffer, filePath?: string, m
|
|||
*
|
||||
* @returns
|
||||
*/
|
||||
export function requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, node: INode, additionalData: IWorkflowExecuteAdditionalData, oAuth2Options?: IOAuth2Options) {
|
||||
const credentials = this.getCredentials(credentialsType) as ICredentialDataDecryptedObject;
|
||||
export async function requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, node: INode, additionalData: IWorkflowExecuteAdditionalData, oAuth2Options?: IOAuth2Options) {
|
||||
const credentials = await this.getCredentials(credentialsType) as ICredentialDataDecryptedObject;
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
|
@ -229,8 +246,8 @@ export function requestOAuth2(this: IAllExecuteFunctions, credentialsType: strin
|
|||
* @param {(OptionsWithUrl | requestPromise.RequestPromiseOptions)} requestOptionså
|
||||
* @returns
|
||||
*/
|
||||
export function requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | OptionsWithUri | requestPromise.RequestPromiseOptions) {
|
||||
const credentials = this.getCredentials(credentialsType) as ICredentialDataDecryptedObject;
|
||||
export async function requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | OptionsWithUri | requestPromise.RequestPromiseOptions) {
|
||||
const credentials = await this.getCredentials(credentialsType) as ICredentialDataDecryptedObject;
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
|
@ -307,6 +324,23 @@ export function returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExe
|
|||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the additional keys for Expressions and Function-Nodes
|
||||
*
|
||||
* @export
|
||||
* @param {IWorkflowExecuteAdditionalData} additionalData
|
||||
* @returns {(IWorkflowDataProxyAdditionalKeys)}
|
||||
*/
|
||||
export function getAdditionalKeys(additionalData: IWorkflowExecuteAdditionalData): IWorkflowDataProxyAdditionalKeys {
|
||||
const executionId = additionalData.executionId || PLACEHOLDER_EMPTY_EXECUTION_ID;
|
||||
return {
|
||||
$executionId: executionId,
|
||||
$resumeWebhookUrl: `${additionalData.webhookWaitingBaseUrl}/${executionId}`,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the requested decrypted credentials if the node has access to them.
|
||||
*
|
||||
|
@ -317,7 +351,7 @@ export function returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExe
|
|||
* @param {IWorkflowExecuteAdditionalData} additionalData
|
||||
* @returns {(ICredentialDataDecryptedObject | undefined)}
|
||||
*/
|
||||
export function getCredentials(workflow: Workflow, node: INode, type: string, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, runExecutionData?: IRunExecutionData | null, runIndex?: number, connectionInputData?: INodeExecutionData[], itemIndex?: number): ICredentialDataDecryptedObject | undefined {
|
||||
export async function getCredentials(workflow: Workflow, node: INode, type: string, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, runExecutionData?: IRunExecutionData | null, runIndex?: number, connectionInputData?: INodeExecutionData[], itemIndex?: number): Promise<ICredentialDataDecryptedObject | undefined> {
|
||||
|
||||
// Get the NodeType as it has the information if the credentials are required
|
||||
const nodeType = workflow.nodeTypes.getByName(node.type);
|
||||
|
@ -371,7 +405,7 @@ export function getCredentials(workflow: Workflow, node: INode, type: string, ad
|
|||
|
||||
const name = node.credentials[type];
|
||||
|
||||
const decryptedDataObject = additionalData.credentialsHelper.getDecrypted(name, type, mode, false, expressionResolveValues);
|
||||
const decryptedDataObject = await additionalData.credentialsHelper.getDecrypted(name, type, mode, false, expressionResolveValues);
|
||||
|
||||
return decryptedDataObject;
|
||||
}
|
||||
|
@ -405,7 +439,7 @@ export function getNode(node: INode): INode {
|
|||
* @param {*} [fallbackValue]
|
||||
* @returns {(NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object)}
|
||||
*/
|
||||
export function getNodeParameter(workflow: Workflow, runExecutionData: IRunExecutionData | null, runIndex: number, connectionInputData: INodeExecutionData[], node: INode, parameterName: string, itemIndex: number, mode: WorkflowExecuteMode, fallbackValue?: any): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object { //tslint:disable-line:no-any
|
||||
export function getNodeParameter(workflow: Workflow, runExecutionData: IRunExecutionData | null, runIndex: number, connectionInputData: INodeExecutionData[], node: INode, parameterName: string, itemIndex: number, mode: WorkflowExecuteMode, additionalKeys: IWorkflowDataProxyAdditionalKeys, fallbackValue?: any): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object { //tslint:disable-line:no-any
|
||||
const nodeType = workflow.nodeTypes.getByName(node.type);
|
||||
if (nodeType === undefined) {
|
||||
throw new Error(`Node type "${node.type}" is not known so can not return paramter value!`);
|
||||
|
@ -419,7 +453,7 @@ export function getNodeParameter(workflow: Workflow, runExecutionData: IRunExecu
|
|||
|
||||
let returnData;
|
||||
try {
|
||||
returnData = workflow.expression.getParameterValue(value, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, mode);
|
||||
returnData = workflow.expression.getParameterValue(value, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, mode, additionalKeys);
|
||||
} catch (e) {
|
||||
e.message += ` [Error in parameter: "${parameterName}"]`;
|
||||
throw e;
|
||||
|
@ -454,7 +488,7 @@ export function continueOnFail(node: INode): boolean {
|
|||
* @param {boolean} [isTest]
|
||||
* @returns {(string | undefined)}
|
||||
*/
|
||||
export function getNodeWebhookUrl(name: string, workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, isTest?: boolean): string | undefined {
|
||||
export function getNodeWebhookUrl(name: string, workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, additionalKeys: IWorkflowDataProxyAdditionalKeys, isTest?: boolean): string | undefined {
|
||||
let baseUrl = additionalData.webhookBaseUrl;
|
||||
if (isTest === true) {
|
||||
baseUrl = additionalData.webhookTestBaseUrl;
|
||||
|
@ -465,12 +499,12 @@ export function getNodeWebhookUrl(name: string, workflow: Workflow, node: INode,
|
|||
return undefined;
|
||||
}
|
||||
|
||||
const path = workflow.expression.getSimpleParameterValue(node, webhookDescription['path'], mode);
|
||||
const path = workflow.expression.getSimpleParameterValue(node, webhookDescription['path'], mode, additionalKeys);
|
||||
if (path === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const isFullPath: boolean = workflow.expression.getSimpleParameterValue(node, webhookDescription['isFullPath'], mode, false) as boolean;
|
||||
const isFullPath: boolean = workflow.expression.getSimpleParameterValue(node, webhookDescription['isFullPath'], mode, additionalKeys, false) as boolean;
|
||||
return NodeHelpers.getNodeWebhookUrl(baseUrl, workflow.id!, node, path.toString(), isFullPath);
|
||||
}
|
||||
|
||||
|
@ -555,8 +589,8 @@ export function getExecutePollFunctions(workflow: Workflow, node: INode, additio
|
|||
__emit: (data: INodeExecutionData[][]): void => {
|
||||
throw new Error('Overwrite NodeExecuteFunctions.getExecutePullFunctions.__emit function!');
|
||||
},
|
||||
getCredentials(type: string): ICredentialDataDecryptedObject | undefined {
|
||||
return getCredentials(workflow, node, type, additionalData, mode);
|
||||
async getCredentials(type: string): Promise<ICredentialDataDecryptedObject | undefined> {
|
||||
return await getCredentials(workflow, node, type, additionalData, mode);
|
||||
},
|
||||
getMode: (): WorkflowExecuteMode => {
|
||||
return mode;
|
||||
|
@ -573,7 +607,7 @@ export function getExecutePollFunctions(workflow: Workflow, node: INode, additio
|
|||
const runIndex = 0;
|
||||
const connectionInputData: INodeExecutionData[] = [];
|
||||
|
||||
return getNodeParameter(workflow, runExecutionData, runIndex, connectionInputData, node, parameterName, itemIndex, mode, fallbackValue);
|
||||
return getNodeParameter(workflow, runExecutionData, runIndex, connectionInputData, node, parameterName, itemIndex, mode, getAdditionalKeys(additionalData), fallbackValue);
|
||||
},
|
||||
getRestApiUrl: (): string => {
|
||||
return additionalData.restApiUrl;
|
||||
|
@ -621,8 +655,8 @@ export function getExecuteTriggerFunctions(workflow: Workflow, node: INode, addi
|
|||
emit: (data: INodeExecutionData[][]): void => {
|
||||
throw new Error('Overwrite NodeExecuteFunctions.getExecuteTriggerFunctions.emit function!');
|
||||
},
|
||||
getCredentials(type: string): ICredentialDataDecryptedObject | undefined {
|
||||
return getCredentials(workflow, node, type, additionalData, mode);
|
||||
async getCredentials(type: string): Promise<ICredentialDataDecryptedObject | undefined> {
|
||||
return await getCredentials(workflow, node, type, additionalData, mode);
|
||||
},
|
||||
getNode: () => {
|
||||
return getNode(node);
|
||||
|
@ -639,7 +673,7 @@ export function getExecuteTriggerFunctions(workflow: Workflow, node: INode, addi
|
|||
const runIndex = 0;
|
||||
const connectionInputData: INodeExecutionData[] = [];
|
||||
|
||||
return getNodeParameter(workflow, runExecutionData, runIndex, connectionInputData, node, parameterName, itemIndex, mode, fallbackValue);
|
||||
return getNodeParameter(workflow, runExecutionData, runIndex, connectionInputData, node, parameterName, itemIndex, mode, getAdditionalKeys(additionalData), fallbackValue);
|
||||
},
|
||||
getRestApiUrl: (): string => {
|
||||
return additionalData.restApiUrl;
|
||||
|
@ -691,7 +725,7 @@ export function getExecuteFunctions(workflow: Workflow, runExecutionData: IRunEx
|
|||
return continueOnFail(node);
|
||||
},
|
||||
evaluateExpression: (expression: string, itemIndex: number) => {
|
||||
return workflow.expression.resolveSimpleParameterValue('=' + expression, {}, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, mode);
|
||||
return workflow.expression.resolveSimpleParameterValue('=' + expression, {}, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, mode, getAdditionalKeys(additionalData));
|
||||
},
|
||||
async executeWorkflow(workflowInfo: IExecuteWorkflowInfo, inputData?: INodeExecutionData[]): Promise<any> { // tslint:disable-line:no-any
|
||||
return additionalData.executeWorkflow(workflowInfo, additionalData, inputData);
|
||||
|
@ -699,8 +733,11 @@ export function getExecuteFunctions(workflow: Workflow, runExecutionData: IRunEx
|
|||
getContext(type: string): IContextObject {
|
||||
return NodeHelpers.getContext(runExecutionData, type, node);
|
||||
},
|
||||
getCredentials(type: string, itemIndex?: number): ICredentialDataDecryptedObject | undefined {
|
||||
return getCredentials(workflow, node, type, additionalData, mode, runExecutionData, runIndex, connectionInputData, itemIndex);
|
||||
async getCredentials(type: string, itemIndex?: number): Promise<ICredentialDataDecryptedObject | undefined> {
|
||||
return await getCredentials(workflow, node, type, additionalData, mode, runExecutionData, runIndex, connectionInputData, itemIndex);
|
||||
},
|
||||
getExecutionId: (): string => {
|
||||
return additionalData.executionId!;
|
||||
},
|
||||
getInputData: (inputIndex = 0, inputName = 'main') => {
|
||||
|
||||
|
@ -714,17 +751,15 @@ export function getExecuteFunctions(workflow: Workflow, runExecutionData: IRunEx
|
|||
throw new Error(`Could not get input index "${inputIndex}" of input "${inputName}"!`);
|
||||
}
|
||||
|
||||
|
||||
if (inputData[inputName][inputIndex] === null) {
|
||||
// return [];
|
||||
throw new Error(`Value "${inputIndex}" of input "${inputName}" did not get set!`);
|
||||
}
|
||||
|
||||
// TODO: Maybe do clone of data only here so it only clones the data that is really needed
|
||||
return inputData[inputName][inputIndex] as INodeExecutionData[];
|
||||
},
|
||||
getNodeParameter: (parameterName: string, itemIndex: number, fallbackValue?: any): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object => { //tslint:disable-line:no-any
|
||||
return getNodeParameter(workflow, runExecutionData, runIndex, connectionInputData, node, parameterName, itemIndex, mode, fallbackValue);
|
||||
return getNodeParameter(workflow, runExecutionData, runIndex, connectionInputData, node, parameterName, itemIndex, mode, getAdditionalKeys(additionalData), fallbackValue);
|
||||
},
|
||||
getMode: (): WorkflowExecuteMode => {
|
||||
return mode;
|
||||
|
@ -742,14 +777,17 @@ export function getExecuteFunctions(workflow: Workflow, runExecutionData: IRunEx
|
|||
return getWorkflowMetadata(workflow);
|
||||
},
|
||||
getWorkflowDataProxy: (itemIndex: number): IWorkflowDataProxyData => {
|
||||
const dataProxy = new WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, {}, mode);
|
||||
const dataProxy = new WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, {}, mode, getAdditionalKeys(additionalData));
|
||||
return dataProxy.getDataProxy();
|
||||
},
|
||||
getWorkflowStaticData(type: string): IDataObject {
|
||||
return workflow.getStaticData(type, node);
|
||||
},
|
||||
prepareOutputData: NodeHelpers.prepareOutputData,
|
||||
sendMessageToUI(message: any): void { // tslint:disable-line:no-any
|
||||
async putExecutionToWait(waitTill: Date): Promise<void> {
|
||||
runExecutionData.waitTill = waitTill;
|
||||
},
|
||||
sendMessageToUI(message : any): void { // tslint:disable-line:no-any
|
||||
if (mode !== 'manual') {
|
||||
return;
|
||||
}
|
||||
|
@ -763,6 +801,9 @@ export function getExecuteFunctions(workflow: Workflow, runExecutionData: IRunEx
|
|||
},
|
||||
helpers: {
|
||||
prepareBinaryData,
|
||||
getBinaryDataBuffer(itemIndex: number, propertyName: string, inputIndex = 0): Promise<Buffer> {
|
||||
return getBinaryDataBuffer.call(this, inputData, itemIndex, propertyName, inputIndex);
|
||||
},
|
||||
request: requestPromiseWithDefaults,
|
||||
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any> { // tslint:disable-line:no-any
|
||||
return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, oAuth2Options);
|
||||
|
@ -801,13 +842,13 @@ export function getExecuteSingleFunctions(workflow: Workflow, runExecutionData:
|
|||
},
|
||||
evaluateExpression: (expression: string, evaluateItemIndex: number | undefined) => {
|
||||
evaluateItemIndex = evaluateItemIndex === undefined ? itemIndex : evaluateItemIndex;
|
||||
return workflow.expression.resolveSimpleParameterValue('=' + expression, {}, runExecutionData, runIndex, evaluateItemIndex, node.name, connectionInputData, mode);
|
||||
return workflow.expression.resolveSimpleParameterValue('=' + expression, {}, runExecutionData, runIndex, evaluateItemIndex, node.name, connectionInputData, mode, getAdditionalKeys(additionalData));
|
||||
},
|
||||
getContext(type: string): IContextObject {
|
||||
return NodeHelpers.getContext(runExecutionData, type, node);
|
||||
},
|
||||
getCredentials(type: string): ICredentialDataDecryptedObject | undefined {
|
||||
return getCredentials(workflow, node, type, additionalData, mode, runExecutionData, runIndex, connectionInputData, itemIndex);
|
||||
async getCredentials(type: string): Promise<ICredentialDataDecryptedObject | undefined> {
|
||||
return await getCredentials(workflow, node, type, additionalData, mode, runExecutionData, runIndex, connectionInputData, itemIndex);
|
||||
},
|
||||
getInputData: (inputIndex = 0, inputName = 'main') => {
|
||||
if (!inputData.hasOwnProperty(inputName)) {
|
||||
|
@ -847,13 +888,13 @@ export function getExecuteSingleFunctions(workflow: Workflow, runExecutionData:
|
|||
return getTimezone(workflow, additionalData);
|
||||
},
|
||||
getNodeParameter: (parameterName: string, fallbackValue?: any): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object => { //tslint:disable-line:no-any
|
||||
return getNodeParameter(workflow, runExecutionData, runIndex, connectionInputData, node, parameterName, itemIndex, mode, fallbackValue);
|
||||
return getNodeParameter(workflow, runExecutionData, runIndex, connectionInputData, node, parameterName, itemIndex, mode, getAdditionalKeys(additionalData), fallbackValue);
|
||||
},
|
||||
getWorkflow: () => {
|
||||
return getWorkflowMetadata(workflow);
|
||||
},
|
||||
getWorkflowDataProxy: (): IWorkflowDataProxyData => {
|
||||
const dataProxy = new WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, {}, mode);
|
||||
const dataProxy = new WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, node.name, connectionInputData, {}, mode, getAdditionalKeys(additionalData));
|
||||
return dataProxy.getDataProxy();
|
||||
},
|
||||
getWorkflowStaticData(type: string): IDataObject {
|
||||
|
@ -886,8 +927,8 @@ export function getExecuteSingleFunctions(workflow: Workflow, runExecutionData:
|
|||
export function getLoadOptionsFunctions(workflow: Workflow, node: INode, path: string, additionalData: IWorkflowExecuteAdditionalData): ILoadOptionsFunctions {
|
||||
return ((workflow: Workflow, node: INode, path: string) => {
|
||||
const that = {
|
||||
getCredentials(type: string): ICredentialDataDecryptedObject | undefined {
|
||||
return getCredentials(workflow, node, type, additionalData, 'internal');
|
||||
async getCredentials(type: string): Promise<ICredentialDataDecryptedObject | undefined> {
|
||||
return await getCredentials(workflow, node, type, additionalData, 'internal');
|
||||
},
|
||||
getCurrentNodeParameter: (parameterPath: string): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object | undefined => {
|
||||
const nodeParameters = additionalData.currentNodeParameters;
|
||||
|
@ -910,7 +951,7 @@ export function getLoadOptionsFunctions(workflow: Workflow, node: INode, path: s
|
|||
const runIndex = 0;
|
||||
const connectionInputData: INodeExecutionData[] = [];
|
||||
|
||||
return getNodeParameter(workflow, runExecutionData, runIndex, connectionInputData, node, parameterName, itemIndex, 'internal' as WorkflowExecuteMode, fallbackValue);
|
||||
return getNodeParameter(workflow, runExecutionData, runIndex, connectionInputData, node, parameterName, itemIndex, 'internal' as WorkflowExecuteMode, getAdditionalKeys(additionalData), fallbackValue);
|
||||
},
|
||||
getTimezone: (): string => {
|
||||
return getTimezone(workflow, additionalData);
|
||||
|
@ -947,8 +988,8 @@ export function getLoadOptionsFunctions(workflow: Workflow, node: INode, path: s
|
|||
export function getExecuteHookFunctions(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, activation: WorkflowActivateMode, isTest?: boolean, webhookData?: IWebhookData): IHookFunctions {
|
||||
return ((workflow: Workflow, node: INode) => {
|
||||
const that = {
|
||||
getCredentials(type: string): ICredentialDataDecryptedObject | undefined {
|
||||
return getCredentials(workflow, node, type, additionalData, mode);
|
||||
async getCredentials(type: string): Promise<ICredentialDataDecryptedObject | undefined> {
|
||||
return await getCredentials(workflow, node, type, additionalData, mode);
|
||||
},
|
||||
getMode: (): WorkflowExecuteMode => {
|
||||
return mode;
|
||||
|
@ -965,10 +1006,10 @@ export function getExecuteHookFunctions(workflow: Workflow, node: INode, additio
|
|||
const runIndex = 0;
|
||||
const connectionInputData: INodeExecutionData[] = [];
|
||||
|
||||
return getNodeParameter(workflow, runExecutionData, runIndex, connectionInputData, node, parameterName, itemIndex, mode, fallbackValue);
|
||||
return getNodeParameter(workflow, runExecutionData, runIndex, connectionInputData, node, parameterName, itemIndex, mode, getAdditionalKeys(additionalData), fallbackValue);
|
||||
},
|
||||
getNodeWebhookUrl: (name: string): string | undefined => {
|
||||
return getNodeWebhookUrl(name, workflow, node, additionalData, mode, isTest);
|
||||
return getNodeWebhookUrl(name, workflow, node, additionalData, mode, getAdditionalKeys(additionalData), isTest);
|
||||
},
|
||||
getTimezone: (): string => {
|
||||
return getTimezone(workflow, additionalData);
|
||||
|
@ -1024,8 +1065,8 @@ export function getExecuteWebhookFunctions(workflow: Workflow, node: INode, addi
|
|||
}
|
||||
return additionalData.httpRequest.body;
|
||||
},
|
||||
getCredentials(type: string): ICredentialDataDecryptedObject | undefined {
|
||||
return getCredentials(workflow, node, type, additionalData, mode);
|
||||
async getCredentials(type: string): Promise<ICredentialDataDecryptedObject | undefined> {
|
||||
return await getCredentials(workflow, node, type, additionalData, mode);
|
||||
},
|
||||
getHeaderData(): object {
|
||||
if (additionalData.httpRequest === undefined) {
|
||||
|
@ -1045,7 +1086,7 @@ export function getExecuteWebhookFunctions(workflow: Workflow, node: INode, addi
|
|||
const runIndex = 0;
|
||||
const connectionInputData: INodeExecutionData[] = [];
|
||||
|
||||
return getNodeParameter(workflow, runExecutionData, runIndex, connectionInputData, node, parameterName, itemIndex, mode, fallbackValue);
|
||||
return getNodeParameter(workflow, runExecutionData, runIndex, connectionInputData, node, parameterName, itemIndex, mode, getAdditionalKeys(additionalData), fallbackValue);
|
||||
},
|
||||
getParamsData(): object {
|
||||
if (additionalData.httpRequest === undefined) {
|
||||
|
@ -1072,7 +1113,7 @@ export function getExecuteWebhookFunctions(workflow: Workflow, node: INode, addi
|
|||
return additionalData.httpResponse;
|
||||
},
|
||||
getNodeWebhookUrl: (name: string): string | undefined => {
|
||||
return getNodeWebhookUrl(name, workflow, node, additionalData, mode);
|
||||
return getNodeWebhookUrl(name, workflow, node, additionalData, mode, getAdditionalKeys(additionalData));
|
||||
},
|
||||
getTimezone: (): string => {
|
||||
return getTimezone(workflow, additionalData);
|
||||
|
|
|
@ -31,7 +31,6 @@ export class WorkflowExecute {
|
|||
private additionalData: IWorkflowExecuteAdditionalData;
|
||||
private mode: WorkflowExecuteMode;
|
||||
|
||||
|
||||
constructor(additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, runExecutionData?: IRunExecutionData) {
|
||||
this.additionalData = additionalData;
|
||||
this.mode = mode;
|
||||
|
@ -512,6 +511,13 @@ export class WorkflowExecute {
|
|||
this.runExecutionData.startData = {};
|
||||
}
|
||||
|
||||
if (this.runExecutionData.waitTill) {
|
||||
const lastNodeExecuted = this.runExecutionData.resultData.lastNodeExecuted as string;
|
||||
this.runExecutionData.executionData!.nodeExecutionStack[0].node.disabled = true;
|
||||
this.runExecutionData.waitTill = undefined;
|
||||
this.runExecutionData.resultData.runData[lastNodeExecuted].pop();
|
||||
}
|
||||
|
||||
let currentExecutionTry = '';
|
||||
let lastExecutionTry = '';
|
||||
|
||||
|
@ -693,7 +699,7 @@ export class WorkflowExecute {
|
|||
}
|
||||
}
|
||||
|
||||
if (nodeSuccessData === null) {
|
||||
if (nodeSuccessData === null && !this.runExecutionData.waitTill!!) {
|
||||
// If null gets returned it means that the node did succeed
|
||||
// but did not have any data. So the branch should end
|
||||
// (meaning the nodes afterwards should not be processed)
|
||||
|
@ -767,6 +773,15 @@ export class WorkflowExecute {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (this.runExecutionData.waitTill!!) {
|
||||
await this.executeHook('nodeExecuteAfter', [executionNode.name, taskData, this.runExecutionData]);
|
||||
|
||||
// Add the node back to the stack that the workflow can start to execute again from that node
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.unshift(executionData);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Add the nodes to which the current node has an output connection to that they can
|
||||
// be executed next
|
||||
if (workflow.connectionsBySourceNode.hasOwnProperty(executionNode.name)) {
|
||||
|
@ -849,6 +864,9 @@ export class WorkflowExecute {
|
|||
message: executionError.message,
|
||||
stack: executionError.stack,
|
||||
} as ExecutionError;
|
||||
} else if (this.runExecutionData.waitTill!!) {
|
||||
Logger.verbose(`Workflow execution will wait until ${this.runExecutionData.waitTill}`, { workflowId: workflow.id });
|
||||
fullRunData.waitTill = this.runExecutionData.waitTill;
|
||||
} else {
|
||||
Logger.verbose(`Workflow execution finished successfully`, { workflowId: workflow.id });
|
||||
fullRunData.finished = true;
|
||||
|
|
|
@ -26,12 +26,14 @@ import {
|
|||
|
||||
|
||||
export class CredentialsHelper extends ICredentialsHelper {
|
||||
getDecrypted(name: string, type: string): ICredentialDataDecryptedObject {
|
||||
return {};
|
||||
getDecrypted(name: string, type: string): Promise<ICredentialDataDecryptedObject> {
|
||||
return new Promise(res => res({}));
|
||||
}
|
||||
|
||||
getCredentials(name: string, type: string): Credentials {
|
||||
return new Credentials('', '', [], '');
|
||||
getCredentials(name: string, type: string): Promise<Credentials> {
|
||||
return new Promise(res => {
|
||||
res(new Credentials('', '', [], ''));
|
||||
});
|
||||
}
|
||||
|
||||
async updateCredentials(name: string, type: string, data: ICredentialDataDecryptedObject): Promise<void> {}
|
||||
|
@ -748,8 +750,7 @@ export function WorkflowExecuteAdditionalData(waitPromise: IDeferredPromise<IRun
|
|||
};
|
||||
|
||||
return {
|
||||
credentials: {},
|
||||
credentialsHelper: new CredentialsHelper({}, ''),
|
||||
credentialsHelper: new CredentialsHelper(''),
|
||||
hooks: new WorkflowHooks(hookFunctions, 'trigger', '1', workflowData),
|
||||
executeWorkflow: async (workflowInfo: IExecuteWorkflowInfo): Promise<any> => {}, // tslint:disable-line:no-any
|
||||
sendMessageToUI: (message: string) => {},
|
||||
|
@ -757,6 +758,7 @@ export function WorkflowExecuteAdditionalData(waitPromise: IDeferredPromise<IRun
|
|||
encryptionKey: 'test',
|
||||
timezone: 'America/New_York',
|
||||
webhookBaseUrl: 'webhook',
|
||||
webhookWaitingBaseUrl: 'webhook-waiting',
|
||||
webhookTestBaseUrl: 'webhook-test',
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1348,7 +1348,7 @@ describe('WorkflowExecute', () => {
|
|||
|
||||
const workflowExecute = new WorkflowExecute(additionalData, executionMode);
|
||||
|
||||
const executionData = await workflowExecute.run(workflowInstance, undefined);
|
||||
const executionData = await workflowExecute.run(workflowInstance);
|
||||
|
||||
const result = await waitPromise.promise();
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-editor-ui",
|
||||
"version": "0.102.0",
|
||||
"version": "0.103.0",
|
||||
"description": "Workflow Editor UI for n8n",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -72,7 +72,7 @@
|
|||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.set": "^4.3.2",
|
||||
"n8n-workflow": "~0.64.0",
|
||||
"n8n-workflow": "~0.65.0",
|
||||
"node-sass": "^4.12.0",
|
||||
"normalize-wheel": "^1.0.1",
|
||||
"prismjs": "^1.17.1",
|
||||
|
|
|
@ -353,6 +353,7 @@ export interface IExecutionsSummary {
|
|||
finished?: boolean;
|
||||
retryOf?: string;
|
||||
retrySuccessId?: string;
|
||||
waitTill?: Date;
|
||||
startedAt: Date;
|
||||
stoppedAt?: Date;
|
||||
workflowId: string;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
:eventBus="modalBus"
|
||||
@enter="save"
|
||||
size="sm"
|
||||
title="Duplicate Workflow"
|
||||
title="Duplicate Workflow"
|
||||
>
|
||||
<template v-slot:content>
|
||||
<el-row>
|
||||
|
@ -109,7 +109,7 @@ export default mixins(showMessage, workflowHelpers).extend({
|
|||
|
||||
this.$data.isSaving = true;
|
||||
|
||||
const saved = await this.saveAsNewWorkflow({name, tags: this.currentTagIds});
|
||||
const saved = await this.saveAsNewWorkflow({name, tags: this.currentTagIds, resetWebhookUrls: true});
|
||||
|
||||
if (saved) {
|
||||
this.closeDialog();
|
||||
|
@ -122,4 +122,4 @@ export default mixins(showMessage, workflowHelpers).extend({
|
|||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
|
|
@ -82,7 +82,10 @@
|
|||
<el-tooltip placement="top" effect="light">
|
||||
<div slot="content" v-html="statusTooltipText(scope.row)"></div>
|
||||
|
||||
<span class="status-badge running" v-if="scope.row.stoppedAt === undefined">
|
||||
<span class="status-badge running" v-if="scope.row.waitTill">
|
||||
Waiting
|
||||
</span>
|
||||
<span class="status-badge running" v-else-if="scope.row.stoppedAt === undefined">
|
||||
Running
|
||||
</span>
|
||||
<span class="status-badge success" v-else-if="scope.row.finished">
|
||||
|
@ -98,7 +101,7 @@
|
|||
|
||||
<el-dropdown trigger="click" @command="handleRetryClick">
|
||||
<span class="el-dropdown-link">
|
||||
<el-button class="retry-button" v-bind:class="{ warning: scope.row.stoppedAt === null }" circle v-if="scope.row.stoppedAt !== undefined && !scope.row.finished && scope.row.retryOf === undefined && scope.row.retrySuccessId === undefined" type="text" size="small" title="Retry execution">
|
||||
<el-button class="retry-button" v-bind:class="{ warning: scope.row.stoppedAt === null }" circle v-if="scope.row.stoppedAt !== undefined && !scope.row.finished && scope.row.retryOf === undefined && scope.row.retrySuccessId === undefined && scope.row.waitTill === undefined" type="text" size="small" title="Retry execution">
|
||||
<font-awesome-icon icon="redo" />
|
||||
</el-button>
|
||||
</span>
|
||||
|
@ -128,12 +131,12 @@
|
|||
</el-table-column>
|
||||
<el-table-column label="" width="100" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.stoppedAt === undefined">
|
||||
<span v-if="scope.row.stoppedAt === undefined || scope.row.waitTill" class="execution-actions">
|
||||
<el-button circle title="Stop Execution" @click.stop="stopExecution(scope.row.id)" :loading="stoppingExecutions.includes(scope.row.id)" size="mini">
|
||||
<font-awesome-icon icon="stop" />
|
||||
</el-button>
|
||||
</span>
|
||||
<span v-else-if="scope.row.id">
|
||||
<span v-if="scope.row.stoppedAt !== undefined && scope.row.id" class="execution-actions">
|
||||
<el-button circle title="Open Past Execution" @click.stop="displayExecution(scope.row)" size="mini">
|
||||
<font-awesome-icon icon="folder-open" />
|
||||
</el-button>
|
||||
|
@ -159,6 +162,8 @@ import ExecutionTime from '@/components/ExecutionTime.vue';
|
|||
import WorkflowActivator from '@/components/WorkflowActivator.vue';
|
||||
|
||||
import { externalHooks } from '@/components/mixins/externalHooks';
|
||||
import { WAIT_TIME_UNLIMITED } from '@/constants';
|
||||
|
||||
import { restApi } from '@/components/mixins/restApi';
|
||||
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
||||
import { showMessage } from '@/components/mixins/showMessage';
|
||||
|
@ -235,6 +240,10 @@ export default mixins(
|
|||
id: 'success',
|
||||
name: 'Success',
|
||||
},
|
||||
{
|
||||
id: 'waiting',
|
||||
name: 'Waiting',
|
||||
},
|
||||
],
|
||||
|
||||
};
|
||||
|
@ -249,7 +258,7 @@ export default mixins(
|
|||
if (['ALL', 'running'].includes(this.filter.status)) {
|
||||
returnData.push.apply(returnData, this.activeExecutions);
|
||||
}
|
||||
if (['ALL', 'error', 'success'].includes(this.filter.status)) {
|
||||
if (['ALL', 'error', 'success', 'waiting'].includes(this.filter.status)) {
|
||||
returnData.push.apply(returnData, this.finishedExecutions);
|
||||
}
|
||||
|
||||
|
@ -287,7 +296,9 @@ export default mixins(
|
|||
if (this.filter.workflowId !== 'ALL') {
|
||||
filter.workflowId = this.filter.workflowId;
|
||||
}
|
||||
if (['error', 'success'].includes(this.filter.status)) {
|
||||
if (this.filter.status === 'waiting') {
|
||||
filter.waitTill = true;
|
||||
} else if (['error', 'success'].includes(this.filter.status)) {
|
||||
filter.finished = this.filter.status === 'success';
|
||||
}
|
||||
return filter;
|
||||
|
@ -609,7 +620,13 @@ export default mixins(
|
|||
this.isDataLoading = false;
|
||||
},
|
||||
statusTooltipText (entry: IExecutionsSummary): string {
|
||||
if (entry.stoppedAt === undefined) {
|
||||
if (entry.waitTill) {
|
||||
const waitDate = new Date(entry.waitTill);
|
||||
if (waitDate.toISOString() === WAIT_TIME_UNLIMITED) {
|
||||
return 'The workflow is waiting indefinitely for an incoming webhook call.';
|
||||
}
|
||||
return `The worklow is waiting till ${waitDate.toLocaleDateString()} ${waitDate.toLocaleTimeString()}.`;
|
||||
} else if (entry.stoppedAt === undefined) {
|
||||
return 'The worklow is currently executing.';
|
||||
} else if (entry.finished === true && entry.retryOf !== undefined) {
|
||||
return `The workflow execution was a retry of "${entry.retryOf}" and it was successful.`;
|
||||
|
@ -659,6 +676,12 @@ export default mixins(
|
|||
text-align: right;
|
||||
}
|
||||
|
||||
.execution-actions {
|
||||
button {
|
||||
margin: 0 0.25em;
|
||||
}
|
||||
}
|
||||
|
||||
.filters {
|
||||
line-height: 2em;
|
||||
.refresh-button {
|
||||
|
|
|
@ -29,7 +29,7 @@ export default Vue.extend({
|
|||
$--horiz-padding: 15px;
|
||||
|
||||
*,
|
||||
*::after {
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
@enter="submit"
|
||||
/>
|
||||
</span>
|
||||
|
||||
|
||||
<span @click="onClick" class="preview" v-else>
|
||||
<ExpandableInputPreview
|
||||
:value="previewValue || value"
|
||||
|
@ -97,4 +97,4 @@ export default Vue.extend({
|
|||
.preview {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
@ -11,6 +11,12 @@
|
|||
v-if="executionFinished"
|
||||
title="Execution was successful"
|
||||
/>
|
||||
<font-awesome-icon
|
||||
icon="clock"
|
||||
class="execution-icon warning"
|
||||
v-else-if="executionWaiting"
|
||||
title="Execution waiting"
|
||||
/>
|
||||
<font-awesome-icon
|
||||
icon="times"
|
||||
class="execution-icon error"
|
||||
|
@ -59,6 +65,11 @@ export default mixins(titleChange).extend({
|
|||
|
||||
return !!fullExecution && fullExecution.finished;
|
||||
},
|
||||
executionWaiting(): boolean {
|
||||
const fullExecution = this.$store.getters.getWorkflowExecution;
|
||||
|
||||
return !!fullExecution && !!fullExecution.waitTill;
|
||||
},
|
||||
workflowExecution(): IExecutionResponse | null {
|
||||
return this.$store.getters.getWorkflowExecution;
|
||||
},
|
||||
|
@ -84,8 +95,13 @@ export default mixins(titleChange).extend({
|
|||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.execution-icon.success {
|
||||
.execution-icon {
|
||||
&.success {
|
||||
color: $--custom-success-text-light;
|
||||
}
|
||||
&.warning {
|
||||
color: $--custom-running-text;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
|
@ -101,4 +117,4 @@ export default mixins(titleChange).extend({
|
|||
.read-only {
|
||||
align-self: flex-end;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
@ -91,4 +91,4 @@ export default mixins(
|
|||
font-weight: 400;
|
||||
padding: 0 20px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
:custom="true"
|
||||
>
|
||||
<template v-slot="{ shortenedName }">
|
||||
<InlineTextEdit
|
||||
<InlineTextEdit
|
||||
:value="workflowName"
|
||||
:previewValue="shortenedName"
|
||||
:isEditEnabled="isNameEditEnabled"
|
||||
|
@ -45,7 +45,7 @@
|
|||
<span
|
||||
class="add-tag clickable"
|
||||
@click="onTagsEditEnable"
|
||||
>
|
||||
>
|
||||
+ Add tag
|
||||
</span>
|
||||
</div>
|
||||
|
@ -120,7 +120,7 @@ export default mixins(workflowHelpers).extend({
|
|||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
isWorkflowActive: "isActive",
|
||||
isWorkflowActive: "isActive",
|
||||
workflowName: "workflowName",
|
||||
isDirty: "getStateIsDirty",
|
||||
currentWorkflowTagIds: "workflowTags",
|
||||
|
@ -276,4 +276,4 @@ $--header-spacing: 20px;
|
|||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
@ -151,4 +151,4 @@ export default Vue.extend({
|
|||
float: right;
|
||||
margin-left: 5px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
@ -9,6 +9,13 @@
|
|||
</div>
|
||||
<el-badge v-else :hidden="workflowDataItems === 0" class="node-info-icon data-count" :value="workflowDataItems"></el-badge>
|
||||
|
||||
<div v-if="waiting" class="node-info-icon waiting">
|
||||
<el-tooltip placement="top" effect="light">
|
||||
<div slot="content" v-html="waiting"></div>
|
||||
<font-awesome-icon icon="clock" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
|
||||
<div class="node-executing-info" title="Node is executing">
|
||||
<font-awesome-icon icon="sync-alt" spin />
|
||||
</div>
|
||||
|
@ -46,6 +53,7 @@
|
|||
<script lang="ts">
|
||||
|
||||
import Vue from 'vue';
|
||||
import { WAIT_TIME_UNLIMITED } from '@/constants';
|
||||
import { externalHooks } from '@/components/mixins/externalHooks';
|
||||
import { nodeBase } from '@/components/mixins/nodeBase';
|
||||
import { nodeHelpers } from '@/components/mixins/nodeHelpers';
|
||||
|
@ -60,6 +68,8 @@ import NodeIcon from '@/components/NodeIcon.vue';
|
|||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
import { get } from 'lodash';
|
||||
|
||||
export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).extend({
|
||||
name: 'Node',
|
||||
components: {
|
||||
|
@ -125,6 +135,22 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext
|
|||
return 'play';
|
||||
}
|
||||
},
|
||||
waiting (): string | undefined {
|
||||
const workflowExecution = this.$store.getters.getWorkflowExecution;
|
||||
|
||||
if (workflowExecution && workflowExecution.waitTill) {
|
||||
const lastNodeExecuted = get(workflowExecution, 'data.resultData.lastNodeExecuted');
|
||||
if (this.name === lastNodeExecuted) {
|
||||
const waitDate = new Date(workflowExecution.waitTill);
|
||||
if (waitDate.toISOString() === WAIT_TIME_UNLIMITED) {
|
||||
return 'The node is waiting indefinitely for an incoming webhook call.';
|
||||
}
|
||||
return `Node is waiting till ${waitDate.toLocaleDateString()} ${waitDate.toLocaleTimeString()}`;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
},
|
||||
workflowRunning (): boolean {
|
||||
return this.$store.getters.isActionActive('workflowRunning');
|
||||
},
|
||||
|
@ -283,12 +309,17 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext
|
|||
position: absolute;
|
||||
top: -18px;
|
||||
right: 12px;
|
||||
z-index: 10;
|
||||
z-index: 11;
|
||||
|
||||
&.data-count {
|
||||
font-weight: 600;
|
||||
top: -12px;
|
||||
}
|
||||
|
||||
&.waiting {
|
||||
left: 10px;
|
||||
top: -12px;
|
||||
}
|
||||
}
|
||||
|
||||
.node-issues {
|
||||
|
@ -298,6 +329,13 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext
|
|||
color: #ff0000;
|
||||
}
|
||||
|
||||
.waiting {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
font-size: 20px;
|
||||
color: #5e5efa;
|
||||
}
|
||||
|
||||
.node-options {
|
||||
display: none;
|
||||
position: absolute;
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
</div>
|
||||
<div class="url-field">
|
||||
<div class="webhook-url left-ellipsis clickable" @click="copyWebhookUrl(webhook)">
|
||||
{{getWebhookUrl(webhook, 'path')}}<br />
|
||||
{{getWebhookUrlDisplay(webhook)}}<br />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -39,6 +39,7 @@
|
|||
|
||||
<script lang="ts">
|
||||
import {
|
||||
INodeTypeDescription,
|
||||
IWebhookDescription,
|
||||
NodeHelpers,
|
||||
} from 'n8n-workflow';
|
||||
|
@ -59,7 +60,7 @@ export default mixins(
|
|||
name: 'NodeWebhooks',
|
||||
props: [
|
||||
'node', // NodeUi
|
||||
'nodeType', // NodeTypeDescription
|
||||
'nodeType', // INodeTypeDescription
|
||||
],
|
||||
data () {
|
||||
return {
|
||||
|
@ -73,7 +74,7 @@ export default mixins(
|
|||
return [];
|
||||
}
|
||||
|
||||
return this.nodeType.webhooks;
|
||||
return (this.nodeType as INodeTypeDescription).webhooks!.filter(webhookData => webhookData.restartWebhook !== true);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
@ -98,6 +99,9 @@ export default mixins(
|
|||
}
|
||||
},
|
||||
getWebhookUrl (webhookData: IWebhookDescription): string {
|
||||
if (webhookData.restartWebhook === true) {
|
||||
return '$resumeWebhookUrl';
|
||||
}
|
||||
let baseUrl = this.$store.getters.getWebhookUrl;
|
||||
if (this.showUrlFor === 'test') {
|
||||
baseUrl = this.$store.getters.getWebhookTestUrl;
|
||||
|
@ -109,6 +113,9 @@ export default mixins(
|
|||
|
||||
return NodeHelpers.getNodeWebhookUrl(baseUrl, workflowId, this.node, path, isFullPath);
|
||||
},
|
||||
getWebhookUrlDisplay (webhookData: IWebhookDescription): string {
|
||||
return this.getWebhookUrl(webhookData);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
node () {
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div v-else-if="parameter.type === 'notice'" v-html="parameter.displayName" class="parameter-item parameter-notice"></div>
|
||||
|
||||
<div
|
||||
v-else-if="['collection', 'fixedCollection'].includes(parameter.type)"
|
||||
class="multi-parameter"
|
||||
|
@ -299,6 +301,17 @@ export default mixins(
|
|||
.parameter-name:hover > .delete-option {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.parameter-notice {
|
||||
background-color: #fff5d3;
|
||||
color: $--custom-font-black;
|
||||
margin: 0.3em 0;
|
||||
padding: 0.8em;
|
||||
|
||||
& a {
|
||||
color: $--color-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -26,4 +26,4 @@ export default Vue.extend({
|
|||
...mapGetters(["pushConnectionActive"]),
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
|
|
@ -320,7 +320,7 @@ $--border-radius: 20px;
|
|||
}
|
||||
|
||||
li {
|
||||
height: $--item-height;
|
||||
height: $--item-height;
|
||||
background-color: white;
|
||||
padding: $--item-padding;
|
||||
margin: 0;
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
@delete="onDelete"
|
||||
@disableCreate="onDisableCreate"
|
||||
/>
|
||||
<NoTagsView
|
||||
<NoTagsView
|
||||
@enableCreate="onEnableCreate"
|
||||
v-else />
|
||||
</el-row>
|
||||
|
@ -114,10 +114,10 @@ export default mixins(showMessage).extend({
|
|||
cb(true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const updatedTag = await this.$store.dispatch("tags/rename", { id, name });
|
||||
cb(!!updatedTag);
|
||||
|
||||
|
||||
const escapedName = escape(name);
|
||||
const escapedOldName = escape(oldName);
|
||||
|
||||
|
@ -183,8 +183,8 @@ export default mixins(showMessage).extend({
|
|||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
<style lang="scss" scoped>
|
||||
.el-row {
|
||||
min-height: $--tags-manager-min-height;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
@ -12,12 +12,17 @@
|
|||
|
||||
<script lang="ts">
|
||||
|
||||
import {
|
||||
PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
|
||||
} from '@/constants';
|
||||
|
||||
import {
|
||||
GenericValue,
|
||||
IContextObject,
|
||||
IDataObject,
|
||||
IRunData,
|
||||
IRunExecutionData,
|
||||
IWorkflowDataProxyAdditionalKeys,
|
||||
Workflow,
|
||||
WorkflowDataProxy,
|
||||
} from 'n8n-workflow';
|
||||
|
@ -376,7 +381,12 @@ export default mixins(
|
|||
return returnData;
|
||||
}
|
||||
|
||||
const dataProxy = new WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, nodeName, connectionInputData, {}, 'manual');
|
||||
const additionalKeys: IWorkflowDataProxyAdditionalKeys = {
|
||||
$executionId: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
|
||||
$resumeWebhookUrl: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
|
||||
};
|
||||
|
||||
const dataProxy = new WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, nodeName, connectionInputData, {}, 'manual', additionalKeys);
|
||||
const proxy = dataProxy.getDataProxy();
|
||||
|
||||
// @ts-ignore
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
import {
|
||||
PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
|
||||
} from '@/constants';
|
||||
|
||||
import {
|
||||
IBinaryKeyData,
|
||||
ICredentialType,
|
||||
|
@ -328,35 +332,35 @@ export const nodeHelpers = mixins(
|
|||
if (data.notesInFlow) {
|
||||
return data.notes;
|
||||
}
|
||||
|
||||
|
||||
if (nodeType !== null && nodeType.subtitle !== undefined) {
|
||||
return workflow.expression.getSimpleParameterValue(data as INode, nodeType.subtitle, 'internal') as string | undefined;
|
||||
return workflow.expression.getSimpleParameterValue(data as INode, nodeType.subtitle, 'internal', PLACEHOLDER_FILLED_AT_EXECUTION_TIME) as string | undefined;
|
||||
}
|
||||
|
||||
|
||||
if (data.parameters.operation !== undefined) {
|
||||
const operation = data.parameters.operation as string;
|
||||
if (nodeType === null) {
|
||||
return operation;
|
||||
}
|
||||
|
||||
|
||||
const operationData:INodeProperties = nodeType.properties.find((property: INodeProperties) => {
|
||||
return property.name === 'operation';
|
||||
});
|
||||
if (operationData === undefined) {
|
||||
return operation;
|
||||
}
|
||||
|
||||
|
||||
if (operationData.options === undefined) {
|
||||
return operation;
|
||||
}
|
||||
|
||||
|
||||
const optionData = operationData.options.find((option) => {
|
||||
return (option as INodePropertyOptions).value === data.parameters.operation;
|
||||
});
|
||||
if (optionData === undefined) {
|
||||
return operation;
|
||||
}
|
||||
|
||||
|
||||
return optionData.name;
|
||||
}
|
||||
return undefined;
|
||||
|
|
|
@ -217,7 +217,15 @@ export const pushConnection = mixins(
|
|||
|
||||
// @ts-ignore
|
||||
const workflow = this.getWorkflow();
|
||||
if (runDataExecuted.finished !== true) {
|
||||
if (runDataExecuted.waitTill !== undefined) {
|
||||
// Workflow did start but had been put to wait
|
||||
this.$titleSet(workflow.name as string, 'IDLE');
|
||||
this.$showMessage({
|
||||
title: 'Workflow got started',
|
||||
message: 'Workflow execution has started and is now waiting!',
|
||||
type: 'success',
|
||||
});
|
||||
} else if (runDataExecuted.finished !== true) {
|
||||
this.$titleSet(workflow.name as string, 'ERROR');
|
||||
|
||||
this.$showMessage({
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import { PLACEHOLDER_EMPTY_WORKFLOW_ID } from '@/constants';
|
||||
import {
|
||||
PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
|
||||
PLACEHOLDER_EMPTY_WORKFLOW_ID,
|
||||
} from '@/constants';
|
||||
|
||||
import {
|
||||
IConnections,
|
||||
|
@ -8,6 +11,7 @@ import {
|
|||
INodeIssues,
|
||||
INodeParameters,
|
||||
NodeParameterValue,
|
||||
INodeCredentials,
|
||||
INodeType,
|
||||
INodeTypes,
|
||||
INodeTypeData,
|
||||
|
@ -15,7 +19,7 @@ import {
|
|||
IRunData,
|
||||
IRunExecutionData,
|
||||
IWorfklowIssues,
|
||||
INodeCredentials,
|
||||
IWorkflowDataProxyAdditionalKeys,
|
||||
Workflow,
|
||||
NodeHelpers,
|
||||
} from 'n8n-workflow';
|
||||
|
@ -39,6 +43,7 @@ import { showMessage } from '@/components/mixins/showMessage';
|
|||
import { isEqual } from 'lodash';
|
||||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
import { v4 as uuidv4} from 'uuid';
|
||||
|
||||
export const workflowHelpers = mixins(
|
||||
externalHooks,
|
||||
|
@ -367,7 +372,12 @@ export const workflowHelpers = mixins(
|
|||
connectionInputData = [];
|
||||
}
|
||||
|
||||
return workflow.expression.getParameterValue(parameter, runExecutionData, runIndex, itemIndex, activeNode.name, connectionInputData, 'manual', false) as IDataObject;
|
||||
const additionalKeys: IWorkflowDataProxyAdditionalKeys = {
|
||||
$executionId: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
|
||||
$resumeWebhookUrl: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
|
||||
};
|
||||
|
||||
return workflow.expression.getParameterValue(parameter, runExecutionData, runIndex, itemIndex, activeNode.name, connectionInputData, 'manual', additionalKeys, false) as IDataObject;
|
||||
},
|
||||
|
||||
resolveExpression(expression: string, siblingParameters: INodeParameters = {}) {
|
||||
|
@ -435,13 +445,21 @@ export const workflowHelpers = mixins(
|
|||
}
|
||||
},
|
||||
|
||||
async saveAsNewWorkflow ({name, tags}: {name?: string, tags?: string[]} = {}): Promise<boolean> {
|
||||
async saveAsNewWorkflow ({name, tags, resetWebhookUrls}: {name?: string, tags?: string[], resetWebhookUrls?: boolean} = {}): Promise<boolean> {
|
||||
try {
|
||||
this.$store.commit('addActiveAction', 'workflowSaving');
|
||||
|
||||
const workflowDataRequest: IWorkflowDataUpdate = await this.getWorkflowDataToSave();
|
||||
// make sure that the new ones are not active
|
||||
workflowDataRequest.active = false;
|
||||
if (resetWebhookUrls) {
|
||||
workflowDataRequest.nodes = workflowDataRequest.nodes!.map(node => {
|
||||
if (node.webhookId) {
|
||||
node.webhookId = uuidv4();
|
||||
}
|
||||
return node;
|
||||
});
|
||||
}
|
||||
|
||||
if (name) {
|
||||
workflowDataRequest.name = name.trim();
|
||||
|
@ -452,10 +470,8 @@ export const workflowHelpers = mixins(
|
|||
}
|
||||
const workflowData = await this.restApi().createNewWorkflow(workflowDataRequest);
|
||||
|
||||
this.$store.commit('setActive', workflowData.active || false);
|
||||
this.$store.commit('setWorkflowId', workflowData.id);
|
||||
this.$store.commit('setWorkflow', workflowData);
|
||||
this.$store.commit('setWorkflowName', {newName: workflowData.name, setStateDirty: false});
|
||||
this.$store.commit('setWorkflowSettings', workflowData.settings || {});
|
||||
this.$store.commit('setStateDirty', false);
|
||||
|
||||
const createdTags = (workflowData.tags || []) as ITag[];
|
||||
|
|
|
@ -2,6 +2,8 @@ export const MAX_DISPLAY_DATA_SIZE = 204800;
|
|||
export const MAX_DISPLAY_ITEMS_AUTO_ALL = 250;
|
||||
export const NODE_NAME_PREFIX = 'node-';
|
||||
|
||||
export const PLACEHOLDER_FILLED_AT_EXECUTION_TIME = '[filled at execution time]';
|
||||
|
||||
// workflows
|
||||
export const PLACEHOLDER_EMPTY_WORKFLOW_ID = '__EMPTY__';
|
||||
export const DEFAULT_NEW_WORKFLOW_NAME = 'My workflow';
|
||||
|
@ -51,4 +53,5 @@ export const HTTP_REQUEST_NODE_NAME = 'n8n-nodes-base.httpRequest';
|
|||
export const REQUEST_NODE_FORM_URL = 'https://n8n-community.typeform.com/to/K1fBVTZ3';
|
||||
|
||||
// General
|
||||
export const INSTANCE_ID_HEADER = 'n8n-instance-id';
|
||||
export const INSTANCE_ID_HEADER = 'n8n-instance-id';
|
||||
export const WAIT_TIME_UNLIMITED = '3000-01-01T00:00:00.000Z';
|
||||
|
|
|
@ -42,6 +42,7 @@ import {
|
|||
faCodeBranch,
|
||||
faCog,
|
||||
faCogs,
|
||||
faClock,
|
||||
faClone,
|
||||
faCloud,
|
||||
faCloudDownloadAlt,
|
||||
|
@ -75,6 +76,7 @@ import {
|
|||
faMapSigns,
|
||||
faNetworkWired,
|
||||
faPause,
|
||||
faPauseCircle,
|
||||
faPen,
|
||||
faPlay,
|
||||
faPlayCircle,
|
||||
|
@ -104,7 +106,6 @@ import {
|
|||
faTrash,
|
||||
faUndo,
|
||||
faUsers,
|
||||
faClock,
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||
|
||||
|
@ -132,6 +133,7 @@ library.add(faCode);
|
|||
library.add(faCodeBranch);
|
||||
library.add(faCog);
|
||||
library.add(faCogs);
|
||||
library.add(faClock);
|
||||
library.add(faClone);
|
||||
library.add(faCloud);
|
||||
library.add(faCloudDownloadAlt);
|
||||
|
@ -165,6 +167,7 @@ library.add(faKey);
|
|||
library.add(faMapSigns);
|
||||
library.add(faNetworkWired);
|
||||
library.add(faPause);
|
||||
library.add(faPauseCircle);
|
||||
library.add(faPen);
|
||||
library.add(faPlay);
|
||||
library.add(faPlayCircle);
|
||||
|
@ -194,7 +197,6 @@ library.add(faTimes);
|
|||
library.add(faTrash);
|
||||
library.add(faUndo);
|
||||
library.add(faUsers);
|
||||
library.add(faClock);
|
||||
|
||||
Vue.component('font-awesome-icon', FontAwesomeIcon);
|
||||
Vue.use(Fragment.Plugin);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-node-dev",
|
||||
"version": "0.19.0",
|
||||
"version": "0.20.0",
|
||||
"description": "CLI to simplify n8n credentials/node development",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -62,8 +62,8 @@
|
|||
"change-case": "^4.1.1",
|
||||
"copyfiles": "^2.1.1",
|
||||
"inquirer": "^7.0.1",
|
||||
"n8n-core": "~0.79.0",
|
||||
"n8n-workflow": "~0.64.0",
|
||||
"n8n-core": "~0.80.0",
|
||||
"n8n-workflow": "~0.65.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"replace-in-file": "^6.0.0",
|
||||
"request": "^2.88.2",
|
||||
|
|
57
packages/nodes-base/credentials/FormIoApi.credentials.ts
Normal file
57
packages/nodes-base/credentials/FormIoApi.credentials.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
INodeProperties,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class FormIoApi implements ICredentialType {
|
||||
name = 'formIoApi';
|
||||
displayName = 'Form.io API';
|
||||
properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Environment',
|
||||
name: 'environment',
|
||||
type: 'options',
|
||||
default: 'cloudHosted',
|
||||
options: [
|
||||
{
|
||||
name: 'Cloud-hosted',
|
||||
value: 'cloudHosted',
|
||||
},
|
||||
{
|
||||
name: 'Self-hosted',
|
||||
value: 'selfHosted',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Self-hosted domain',
|
||||
name: 'domain',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'https://www.mydomain.com',
|
||||
displayOptions: {
|
||||
show: {
|
||||
environment: [
|
||||
'selfHosted',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Email',
|
||||
name: 'email',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Password',
|
||||
name: 'password',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
typeOptions: {
|
||||
password: true,
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
}
|
19
packages/nodes-base/credentials/FormstackApi.credentials.ts
Normal file
19
packages/nodes-base/credentials/FormstackApi.credentials.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
||||
export class FormstackApi implements ICredentialType {
|
||||
name = 'formstackApi';
|
||||
displayName = 'Formstack API';
|
||||
documentationUrl = 'formstack';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Access Token',
|
||||
name: 'accessToken',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
const scopes: string[] = [];
|
||||
|
||||
export class FormstackOAuth2Api implements ICredentialType {
|
||||
name = 'formstackOAuth2Api';
|
||||
extends = [
|
||||
'oAuth2Api',
|
||||
];
|
||||
displayName = 'Formstack OAuth2 API';
|
||||
documentationUrl = 'formstack';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Authorization URL',
|
||||
name: 'authUrl',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'https://www.formstack.com/api/v2/oauth2/authorize',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Access Token URL',
|
||||
name: 'accessTokenUrl',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'https://www.formstack.com/api/v2/oauth2/token',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Scope',
|
||||
name: 'scope',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: scopes.join(' '),
|
||||
},
|
||||
{
|
||||
displayName: 'Auth URI Query Parameters',
|
||||
name: 'authQueryParameters',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Authentication',
|
||||
name: 'authentication',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'header',
|
||||
},
|
||||
];
|
||||
}
|
|
@ -13,7 +13,7 @@ export class GithubApi implements ICredentialType {
|
|||
name: 'server',
|
||||
type: 'string',
|
||||
default: 'https://api.github.com',
|
||||
description: 'The server to connect to. Does only have to get changed if Github Enterprise gets used.',
|
||||
description: 'The server to connect to. Only has to be set if Github Enterprise is used.',
|
||||
},
|
||||
{
|
||||
displayName: 'User',
|
||||
|
|
|
@ -17,7 +17,7 @@ export class GithubOAuth2Api implements ICredentialType {
|
|||
name: 'server',
|
||||
type: 'string',
|
||||
default: 'https://api.github.com',
|
||||
description: 'The server to connect to. Does only have to get changed if Github Enterprise gets used.',
|
||||
description: 'The server to connect to. Only has to be set if Github Enterprise is used.',
|
||||
},
|
||||
{
|
||||
displayName: 'Authorization URL',
|
||||
|
|
|
@ -5,7 +5,7 @@ import {
|
|||
|
||||
export class TwakeCloudApi implements ICredentialType {
|
||||
name = 'twakeCloudApi';
|
||||
displayName = 'Twake API';
|
||||
displayName = 'Twake Cloud API';
|
||||
documentationUrl = 'twake';
|
||||
properties: INodeProperties[] = [
|
||||
{
|
||||
|
|
|
@ -5,7 +5,7 @@ import {
|
|||
|
||||
export class TwakeServerApi implements ICredentialType {
|
||||
name = 'twakeServerApi';
|
||||
displayName = 'Twake API';
|
||||
displayName = 'Twake Server API';
|
||||
documentationUrl = 'twake';
|
||||
properties: INodeProperties[] = [
|
||||
{
|
||||
|
|
|
@ -35,7 +35,7 @@ export async function actionNetworkApiRequest(
|
|||
body: IDataObject = {},
|
||||
qs: IDataObject = {},
|
||||
) {
|
||||
const credentials = this.getCredentials('actionNetworkApi') as { apiKey: string } | undefined;
|
||||
const credentials = await this.getCredentials('actionNetworkApi') as { apiKey: string } | undefined;
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
|
|
|
@ -26,7 +26,7 @@ export interface IProduct {
|
|||
* @returns {Promise<any>}
|
||||
*/
|
||||
export async function activeCampaignApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: IDataObject, query?: IDataObject, dataKey?: string): Promise<any> { // tslint:disable-line:no-any
|
||||
const credentials = this.getCredentials('activeCampaignApi');
|
||||
const credentials = await this.getCredentials('activeCampaignApi');
|
||||
if (credentials === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ export async function acuitySchedulingApiRequest(this: IHookFunctions | IExecute
|
|||
|
||||
try {
|
||||
if (authenticationMethod === 'apiKey') {
|
||||
const credentials = this.getCredentials('acuitySchedulingApi');
|
||||
const credentials = await this.getCredentials('acuitySchedulingApi');
|
||||
if (credentials === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
|
||||
export async function affinityApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const credentials = this.getCredentials('affinityApi');
|
||||
const credentials = await this.getCredentials('affinityApi');
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
|
|
|
@ -17,7 +17,7 @@ import { IContactUpdate } from './ContactInterface';
|
|||
|
||||
export async function agileCrmApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, query: IDataObject = {}, uri?: string): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const credentials = this.getCredentials('agileCrmApi');
|
||||
const credentials = await this.getCredentials('agileCrmApi');
|
||||
const options: OptionsWithUri = {
|
||||
method,
|
||||
headers: {
|
||||
|
@ -46,7 +46,7 @@ export async function agileCrmApiRequest(this: IHookFunctions | IExecuteFunction
|
|||
|
||||
export async function agileCrmApiRequestUpdate(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method = 'PUT', endpoint?: string, body: any = {}, query: IDataObject = {}, uri?: string): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const credentials = this.getCredentials('agileCrmApi');
|
||||
const credentials = await this.getCredentials('agileCrmApi');
|
||||
const baseUri = `https://${credentials!.subdomain}.agilecrm.com/dev/`;
|
||||
const options: OptionsWithUri = {
|
||||
method,
|
||||
|
|
|
@ -40,7 +40,7 @@ export interface IRecord {
|
|||
* @returns {Promise<any>}
|
||||
*/
|
||||
export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions, method: string, endpoint: string, body: object, query?: IDataObject, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
const credentials = this.getCredentials('airtableApi');
|
||||
const credentials = await this.getCredentials('airtableApi');
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
|
|
|
@ -98,7 +98,7 @@ export class Amqp implements INodeType {
|
|||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
try {
|
||||
const credentials = this.getCredentials('amqp');
|
||||
const credentials = await this.getCredentials('amqp');
|
||||
if (!credentials) {
|
||||
throw new NodeOperationError(this.getNode(), 'Credentials are mandatory!');
|
||||
}
|
||||
|
|
|
@ -132,7 +132,7 @@ export class AmqpTrigger implements INodeType {
|
|||
|
||||
async trigger(this: ITriggerFunctions): Promise<ITriggerResponse> {
|
||||
|
||||
const credentials = this.getCredentials('amqp');
|
||||
const credentials = await this.getCredentials('amqp');
|
||||
if (!credentials) {
|
||||
throw new NodeOperationError(this.getNode(), 'Credentials are mandatory!');
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ export async function apiTemplateIoApiRequest(
|
|||
qs = {},
|
||||
body = {},
|
||||
) {
|
||||
const { apiKey } = this.getCredentials('apiTemplateIoApi') as { apiKey: string };
|
||||
const { apiKey } = await this.getCredentials('apiTemplateIoApi') as { apiKey: string };
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers: {
|
||||
|
|
|
@ -42,7 +42,7 @@ export async function asanaApiRequest(this: IHookFunctions | IExecuteFunctions |
|
|||
|
||||
try {
|
||||
if (authenticationMethod === 'accessToken') {
|
||||
const credentials = this.getCredentials('asanaApi');
|
||||
const credentials = await this.getCredentials('asanaApi');
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
|
||||
export async function automizyApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, path: string, body: any = {}, qs: IDataObject = {}, option = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const credentials = this.getCredentials('automizyApi') as IDataObject;
|
||||
const credentials = await this.getCredentials('automizyApi') as IDataObject;
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers: {
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
|
||||
export async function autopilotApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const credentials = this.getCredentials('autopilotApi') as IDataObject;
|
||||
const credentials = await this.getCredentials('autopilotApi') as IDataObject;
|
||||
|
||||
const apiKey = `${credentials.apiKey}`;
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ function getEndpointForService(service: string, credentials: ICredentialDataDecr
|
|||
}
|
||||
|
||||
export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string, headers?: object): Promise<any> { // tslint:disable-line:no-any
|
||||
const credentials = this.getCredentials('aws');
|
||||
const credentials = await this.getCredentials('aws');
|
||||
if (credentials === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ function getEndpointForService(service: string, credentials: ICredentialDataDecr
|
|||
}
|
||||
|
||||
export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: object | IRequestBody, headers?: object): Promise<any> { // tslint:disable-line:no-any
|
||||
const credentials = this.getCredentials('aws');
|
||||
const credentials = await this.getCredentials('aws');
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ function getEndpointForService(service: string, credentials: ICredentialDataDecr
|
|||
}
|
||||
|
||||
export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string, headers?: object): Promise<any> { // tslint:disable-line:no-any
|
||||
const credentials = this.getCredentials('aws');
|
||||
const credentials = await this.getCredentials('aws');
|
||||
if (credentials === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ import {
|
|||
} from 'change-case';
|
||||
|
||||
export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string | Buffer | IDataObject, query: IDataObject = {}, headers?: object, option: IDataObject = {}, region?: string): Promise<any> { // tslint:disable-line:no-any
|
||||
const credentials = this.getCredentials('aws');
|
||||
const credentials = await this.getCredentials('aws');
|
||||
if (credentials === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ import {
|
|||
} from 'xml2js';
|
||||
|
||||
import {
|
||||
BINARY_ENCODING,
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
|
@ -115,7 +114,7 @@ export class AwsS3 implements INodeType {
|
|||
if (resource === 'bucket') {
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateBucket.html
|
||||
if (operation === 'create') {
|
||||
const credentials = this.getCredentials('aws');
|
||||
const credentials = await this.getCredentials('aws');
|
||||
const name = this.getNodeParameter('name', i) as string;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
if (additionalFields.acl) {
|
||||
|
@ -607,8 +606,9 @@ export class AwsS3 implements INodeType {
|
|||
}
|
||||
|
||||
const binaryData = (items[i].binary as IBinaryKeyData)[binaryPropertyName];
|
||||
const binaryDataBuffer = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName);
|
||||
|
||||
body = Buffer.from(binaryData.data, BINARY_ENCODING) as Buffer;
|
||||
body = binaryDataBuffer;
|
||||
|
||||
headers['Content-Type'] = binaryData.mimeType;
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ import {
|
|||
} from 'n8n-workflow';
|
||||
|
||||
export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string | Buffer, query: IDataObject = {}, headers?: object, option: IDataObject = {}, region?: string): Promise<any> { // tslint:disable-line:no-any
|
||||
const credentials = this.getCredentials('aws');
|
||||
const credentials = await this.getCredentials('aws');
|
||||
if (credentials === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ import {
|
|||
} from 'lodash';
|
||||
|
||||
export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string, headers?: object): Promise<any> { // tslint:disable-line:no-any
|
||||
const credentials = this.getCredentials('aws');
|
||||
const credentials = await this.getCredentials('aws');
|
||||
if (credentials === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ function getEndpointForService(service: string, credentials: ICredentialDataDecr
|
|||
}
|
||||
|
||||
export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string, headers?: object): Promise<any> { // tslint:disable-line:no-any
|
||||
const credentials = this.getCredentials('aws');
|
||||
const credentials = await this.getCredentials('aws');
|
||||
if (credentials === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import {
|
|||
|
||||
export async function bannerbearApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, headers: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const credentials = this.getCredentials('bannerbearApi');
|
||||
const credentials = await this.getCredentials('bannerbearApi');
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
|
|
|
@ -114,7 +114,7 @@ export class Baserow implements INodeType {
|
|||
methods = {
|
||||
loadOptions: {
|
||||
async getDatabaseIds(this: ILoadOptionsFunctions) {
|
||||
const credentials = this.getCredentials('baserowApi') as BaserowCredentials;
|
||||
const credentials = await this.getCredentials('baserowApi') as BaserowCredentials;
|
||||
const jwtToken = await getJwtToken.call(this, credentials);
|
||||
const endpoint = '/api/applications/';
|
||||
const databases = await baserowApiRequest.call(this, 'GET', endpoint, {}, {}, jwtToken) as LoadedResource[];
|
||||
|
@ -122,7 +122,7 @@ export class Baserow implements INodeType {
|
|||
},
|
||||
|
||||
async getTableIds(this: ILoadOptionsFunctions) {
|
||||
const credentials = this.getCredentials('baserowApi') as BaserowCredentials;
|
||||
const credentials = await this.getCredentials('baserowApi') as BaserowCredentials;
|
||||
const jwtToken = await getJwtToken.call(this, credentials);
|
||||
const databaseId = this.getNodeParameter('databaseId', 0) as string;
|
||||
const endpoint = `/api/database/tables/database/${databaseId}`;
|
||||
|
@ -131,7 +131,7 @@ export class Baserow implements INodeType {
|
|||
},
|
||||
|
||||
async getTableFields(this: ILoadOptionsFunctions) {
|
||||
const credentials = this.getCredentials('baserowApi') as BaserowCredentials;
|
||||
const credentials = await this.getCredentials('baserowApi') as BaserowCredentials;
|
||||
const jwtToken = await getJwtToken.call(this, credentials);
|
||||
const tableId = this.getNodeParameter('tableId', 0) as string;
|
||||
const endpoint = `/api/database/fields/table/${tableId}/`;
|
||||
|
@ -148,7 +148,7 @@ export class Baserow implements INodeType {
|
|||
const operation = this.getNodeParameter('operation', 0) as Operation;
|
||||
|
||||
const tableId = this.getNodeParameter('tableId', 0) as string;
|
||||
const credentials = this.getCredentials('baserowApi') as BaserowCredentials;
|
||||
const credentials = await this.getCredentials('baserowApi') as BaserowCredentials;
|
||||
const jwtToken = await getJwtToken.call(this, credentials);
|
||||
const fields = await mapper.getTableFields.call(this, tableId, jwtToken);
|
||||
mapper.createMappings(fields);
|
||||
|
|
|
@ -30,7 +30,7 @@ export async function baserowApiRequest(
|
|||
qs: IDataObject = {},
|
||||
jwtToken: string,
|
||||
) {
|
||||
const credentials = this.getCredentials('baserowApi') as BaserowCredentials;
|
||||
const credentials = await this.getCredentials('baserowApi') as BaserowCredentials;
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
} from './GenericFunctions';
|
||||
|
||||
export async function createDatapoint(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, data: IDataObject) {
|
||||
const credentials = this.getCredentials('beeminderApi');
|
||||
const credentials = await this.getCredentials('beeminderApi');
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
|
@ -28,7 +28,7 @@ export async function createDatapoint(this: IExecuteFunctions | IWebhookFunction
|
|||
}
|
||||
|
||||
export async function getAllDatapoints(this: IExecuteFunctions | IHookFunctions | ILoadOptionsFunctions, data: IDataObject) {
|
||||
const credentials = this.getCredentials('beeminderApi');
|
||||
const credentials = await this.getCredentials('beeminderApi');
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
|
@ -44,7 +44,7 @@ export async function getAllDatapoints(this: IExecuteFunctions | IHookFunctions
|
|||
}
|
||||
|
||||
export async function updateDatapoint(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, data: IDataObject) {
|
||||
const credentials = this.getCredentials('beeminderApi');
|
||||
const credentials = await this.getCredentials('beeminderApi');
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
|
@ -56,7 +56,7 @@ export async function updateDatapoint(this: IExecuteFunctions | IWebhookFunction
|
|||
}
|
||||
|
||||
export async function deleteDatapoint(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, data: IDataObject) {
|
||||
const credentials = this.getCredentials('beeminderApi');
|
||||
const credentials = await this.getCredentials('beeminderApi');
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
|
|
|
@ -306,7 +306,7 @@ export class Beeminder implements INodeType {
|
|||
// select them easily
|
||||
async getGoals(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
|
||||
const credentials = this.getCredentials('beeminderApi');
|
||||
const credentials = await this.getCredentials('beeminderApi');
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
|
|
|
@ -18,7 +18,7 @@ const BEEMINDER_URI = 'https://www.beeminder.com/api/v1';
|
|||
|
||||
export async function beeminderApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const credentials = this.getCredentials('beeminderApi') as IDataObject;
|
||||
const credentials = await this.getCredentials('beeminderApi') as IDataObject;
|
||||
|
||||
Object.assign(body, { auth_token: credentials.authToken });
|
||||
|
||||
|
|
|
@ -221,7 +221,7 @@ export class BitbucketTrigger implements INodeType {
|
|||
// Get all the repositories to display them to user so that he can
|
||||
// select them easily
|
||||
async getRepositories(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const credentials = this.getCredentials('bitbucketApi');
|
||||
const credentials = await this.getCredentials('bitbucketApi');
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const repositories = await bitbucketApiRequestAllItems.call(this, 'values', 'GET', `/repositories/${credentials!.username}`);
|
||||
for (const repository of repositories) {
|
||||
|
@ -261,7 +261,7 @@ export class BitbucketTrigger implements INodeType {
|
|||
default: {
|
||||
async checkExists(this: IHookFunctions): Promise<boolean> {
|
||||
let endpoint = '';
|
||||
const credentials = this.getCredentials('bitbucketApi');
|
||||
const credentials = await this.getCredentials('bitbucketApi');
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
if (webhookData.webhookId === undefined) {
|
||||
|
@ -292,7 +292,7 @@ export class BitbucketTrigger implements INodeType {
|
|||
const webhookData = this.getWorkflowStaticData('node');
|
||||
const events = this.getNodeParameter('events') as string[];
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
const credentials = this.getCredentials('bitbucketApi');
|
||||
const credentials = await this.getCredentials('bitbucketApi');
|
||||
|
||||
if (resource === 'user') {
|
||||
endpoint = `/users/${credentials!.username}/hooks`;
|
||||
|
@ -318,7 +318,7 @@ export class BitbucketTrigger implements INodeType {
|
|||
async delete(this: IHookFunctions): Promise<boolean> {
|
||||
let endpoint = '';
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
const credentials = this.getCredentials('bitbucketApi');
|
||||
const credentials = await this.getCredentials('bitbucketApi');
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
if (resource === 'user') {
|
||||
endpoint = `/users/${credentials!.username}/hooks/${webhookData.webhookId}`;
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
import { IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow';
|
||||
|
||||
export async function bitbucketApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
const credentials = this.getCredentials('bitbucketApi');
|
||||
const credentials = await this.getCredentials('bitbucketApi');
|
||||
if (credentials === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ export async function bitlyApiRequest(this: IHookFunctions | IExecuteFunctions |
|
|||
|
||||
try{
|
||||
if (authenticationMethod === 'accessToken') {
|
||||
const credentials = this.getCredentials('bitlyApi');
|
||||
const credentials = await this.getCredentials('bitlyApi');
|
||||
if (credentials === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ export async function bitwardenApiRequest(
|
|||
token: string,
|
||||
): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const baseUrl = await getBaseUrl.call(this);
|
||||
const options: OptionsWithUri = {
|
||||
headers: {
|
||||
'user-agent': 'n8n',
|
||||
|
@ -34,7 +35,7 @@ export async function bitwardenApiRequest(
|
|||
method,
|
||||
qs,
|
||||
body,
|
||||
uri: `${getBaseUrl.call(this)}${endpoint}`,
|
||||
uri: `${baseUrl}${endpoint}`,
|
||||
json: true,
|
||||
};
|
||||
|
||||
|
@ -60,7 +61,7 @@ export async function getAccessToken(
|
|||
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||
): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const credentials = this.getCredentials('bitwardenApi') as IDataObject;
|
||||
const credentials = await this.getCredentials('bitwardenApi') as IDataObject;
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers: {
|
||||
|
@ -76,7 +77,7 @@ export async function getAccessToken(
|
|||
deviceType: 2, // https://github.com/bitwarden/server/blob/master/src/Core/Enums/DeviceType.cs
|
||||
deviceIdentifier: 'n8n',
|
||||
},
|
||||
uri: getTokenUrl.call(this),
|
||||
uri: await getTokenUrl.call(this),
|
||||
json: true,
|
||||
};
|
||||
|
||||
|
@ -114,8 +115,8 @@ export async function handleGetAll(
|
|||
/**
|
||||
* Return the access token URL based on the user's environment.
|
||||
*/
|
||||
function getTokenUrl(this: IExecuteFunctions | ILoadOptionsFunctions) {
|
||||
const { environment, domain } = this.getCredentials('bitwardenApi') as IDataObject;
|
||||
async function getTokenUrl(this: IExecuteFunctions | ILoadOptionsFunctions) {
|
||||
const { environment, domain } = await this.getCredentials('bitwardenApi') as IDataObject;
|
||||
|
||||
return environment === 'cloudHosted'
|
||||
? 'https://identity.bitwarden.com/connect/token'
|
||||
|
@ -126,8 +127,8 @@ function getTokenUrl(this: IExecuteFunctions | ILoadOptionsFunctions) {
|
|||
/**
|
||||
* Return the base API URL based on the user's environment.
|
||||
*/
|
||||
function getBaseUrl(this: IExecuteFunctions | ILoadOptionsFunctions) {
|
||||
const { environment, domain } = this.getCredentials('bitwardenApi') as IDataObject;
|
||||
async function getBaseUrl(this: IExecuteFunctions | ILoadOptionsFunctions) {
|
||||
const { environment, domain } = await this.getCredentials('bitwardenApi') as IDataObject;
|
||||
|
||||
return environment === 'cloudHosted'
|
||||
? 'https://api.bitwarden.com'
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue