n8n/packages/nodes-base/nodes/Wait/Wait.node.ts
2023-07-04 16:17:50 +02:00

397 lines
9.6 KiB
TypeScript

import type {
IExecuteFunctions,
INodeExecutionData,
INodeTypeDescription,
INodeProperties,
IDisplayOptions,
} from 'n8n-workflow';
import { WAIT_TIME_UNLIMITED } from 'n8n-workflow';
import {
authenticationProperty,
credentialsProperty,
defaultWebhookDescription,
httpMethodsProperty,
optionsProperty,
responseBinaryPropertyNameProperty,
responseCodeProperty,
responseDataProperty,
responseModeProperty,
} from '../Webhook/description';
import { Webhook } from '../Webhook/Webhook.node';
const displayOnWebhook: IDisplayOptions = {
show: {
resume: ['webhook'],
},
};
export class Wait extends Webhook {
authPropertyName = 'incomingAuthentication';
description: INodeTypeDescription = {
displayName: 'Wait',
name: 'wait',
icon: 'fa:pause-circle',
group: ['organization'],
version: 1,
description: 'Wait before continue with execution',
defaults: {
name: 'Wait',
color: '#804050',
},
inputs: ['main'],
outputs: ['main'],
credentials: credentialsProperty(this.authPropertyName),
webhooks: [
{
...defaultWebhookDescription,
responseData: '={{$parameter["responseData"]}}',
path: '={{$parameter["options"]["webhookSuffix"] || ""}}',
restartWebhook: true,
},
],
properties: [
{
displayName: 'Resume',
name: 'resume',
type: 'options',
options: [
{
name: 'After Time Interval',
value: 'timeInterval',
description: 'Waits for a certain amount of time',
},
{
name: 'At Specified Time',
value: 'specificTime',
description: 'Waits until a specific date and time to continue',
},
{
name: 'On Webhook Call',
value: 'webhook',
description: 'Waits for a webhook call before continuing',
},
],
default: 'timeInterval',
description: 'Determines the waiting mode to use before the workflow continues',
},
{
...authenticationProperty(this.authPropertyName),
description:
'If and how incoming resume-webhook-requests to $execution.resumeUrl should be authenticated for additional security',
displayOptions: displayOnWebhook,
},
// ----------------------------------
// resume:specificTime
// ----------------------------------
{
displayName: 'Date and Time',
name: 'dateTime',
type: 'dateTime',
displayOptions: {
show: {
resume: ['specificTime'],
},
},
default: '',
description: 'The date and time to wait for before continuing',
},
// ----------------------------------
// resume:timeInterval
// ----------------------------------
{
displayName: 'Wait Amount',
name: 'amount',
type: 'number',
displayOptions: {
show: {
resume: ['timeInterval'],
},
},
typeOptions: {
minValue: 0,
numberPrecision: 2,
},
default: 1,
description: 'The time to wait',
},
{
displayName: 'Wait Unit',
name: 'unit',
type: 'options',
displayOptions: {
show: {
resume: ['timeInterval'],
},
},
options: [
{
name: 'Seconds',
value: 'seconds',
},
{
name: 'Minutes',
value: 'minutes',
},
{
name: 'Hours',
value: 'hours',
},
{
name: 'Days',
value: 'days',
},
],
default: 'hours',
description: 'The time unit of the Wait Amount value',
},
// ----------------------------------
// resume:webhook
// ----------------------------------
{
displayName:
'The webhook URL will be generated at run time. It can be referenced with the <strong>$execution.resumeUrl</strong> variable. Send it somewhere before getting to this node. <a href="https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.wait/?utm_source=n8n_app&utm_medium=node_settings_modal-credential_link&utm_campaign=n8n-nodes-base.wait" target="_blank">More info</a>',
name: 'webhookNotice',
type: 'notice',
displayOptions: displayOnWebhook,
default: '',
},
{
...httpMethodsProperty,
displayOptions: displayOnWebhook,
description: 'The HTTP method of the Webhook call',
},
{
...responseCodeProperty,
displayOptions: displayOnWebhook,
},
{
...responseModeProperty,
displayOptions: displayOnWebhook,
},
{
...responseDataProperty,
displayOptions: {
show: {
...responseDataProperty.displayOptions?.show,
...displayOnWebhook.show,
},
},
},
{
...responseBinaryPropertyNameProperty,
displayOptions: {
show: {
...responseBinaryPropertyNameProperty.displayOptions?.show,
...displayOnWebhook.show,
},
},
},
{
displayName: 'Limit Wait Time',
name: 'limitWaitTime',
type: 'boolean',
default: false,
// eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether
description:
'If no webhook call is received, the workflow will automatically resume execution after the specified limit type',
displayOptions: displayOnWebhook,
},
{
displayName: 'Limit Type',
name: 'limitType',
type: 'options',
default: 'afterTimeInterval',
description:
'Sets the condition for the execution to resume. Can be a specified date or after some time.',
displayOptions: {
show: {
limitWaitTime: [true],
...displayOnWebhook.show,
},
},
options: [
{
name: 'After Time Interval',
description: 'Waits for a certain amount of time',
value: 'afterTimeInterval',
},
{
name: 'At Specified Time',
description: 'Waits until the set date and time to continue',
value: 'atSpecifiedTime',
},
],
},
{
displayName: 'Amount',
name: 'resumeAmount',
type: 'number',
displayOptions: {
show: {
limitType: ['afterTimeInterval'],
limitWaitTime: [true],
...displayOnWebhook.show,
},
},
typeOptions: {
minValue: 0,
numberPrecision: 2,
},
default: 1,
description: 'The time to wait',
},
{
displayName: 'Unit',
name: 'resumeUnit',
type: 'options',
displayOptions: {
show: {
limitType: ['afterTimeInterval'],
limitWaitTime: [true],
...displayOnWebhook.show,
},
},
options: [
{
name: 'Seconds',
value: 'seconds',
},
{
name: 'Minutes',
value: 'minutes',
},
{
name: 'Hours',
value: 'hours',
},
{
name: 'Days',
value: 'days',
},
],
default: 'hours',
description: 'Unit of the interval value',
},
{
displayName: 'Max Date and Time',
name: 'maxDateAndTime',
type: 'dateTime',
displayOptions: {
show: {
limitType: ['atSpecifiedTime'],
limitWaitTime: [true],
...displayOnWebhook.show,
},
},
default: '',
description: 'Continue execution after the specified date and time',
},
{
...optionsProperty,
displayOptions: displayOnWebhook,
options: [
...(optionsProperty.options as INodeProperties[]),
{
displayName: 'Webhook Suffix',
name: 'webhookSuffix',
type: 'string',
default: '',
placeholder: 'webhook',
description:
'This suffix path will be appended to the restart URL. Helpful when using multiple wait nodes. Note: Does not support expressions.',
},
],
},
],
};
async execute(context: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const resume = context.getNodeParameter('resume', 0) as string;
if (resume === 'webhook') {
return this.handleWebhookResume(context);
}
let waitTill: Date;
if (resume === 'timeInterval') {
const unit = context.getNodeParameter('unit', 0) as string;
let waitAmount = context.getNodeParameter('amount', 0) as number;
if (unit === 'minutes') {
waitAmount *= 60;
}
if (unit === 'hours') {
waitAmount *= 60 * 60;
}
if (unit === 'days') {
waitAmount *= 60 * 60 * 24;
}
waitAmount *= 1000;
waitTill = new Date(new Date().getTime() + waitAmount);
} else {
// resume: dateTime
const dateTime = context.getNodeParameter('dateTime', 0) as string;
waitTill = new Date(dateTime);
}
const waitValue = Math.max(waitTill.getTime() - new Date().getTime(), 0);
if (waitValue < 65000) {
// If wait time is shorter than 65 seconds leave execution active because
// we just check the database every 60 seconds.
return new Promise((resolve, _reject) => {
setTimeout(() => {
resolve([context.getInputData()]);
}, waitValue);
});
}
// If longer than 65 seconds put execution to wait
return this.putToWait(context, waitTill);
}
private async handleWebhookResume(context: IExecuteFunctions) {
let waitTill = new Date(WAIT_TIME_UNLIMITED);
const limitWaitTime = context.getNodeParameter('limitWaitTime', 0);
if (limitWaitTime === true) {
const limitType = context.getNodeParameter('limitType', 0);
if (limitType === 'afterTimeInterval') {
let waitAmount = context.getNodeParameter('resumeAmount', 0) as number;
const resumeUnit = context.getNodeParameter('resumeUnit', 0);
if (resumeUnit === 'minutes') {
waitAmount *= 60;
}
if (resumeUnit === 'hours') {
waitAmount *= 60 * 60;
}
if (resumeUnit === 'days') {
waitAmount *= 60 * 60 * 24;
}
waitAmount *= 1000;
waitTill = new Date(new Date().getTime() + waitAmount);
} else {
waitTill = new Date(context.getNodeParameter('maxDateAndTime', 0) as string);
}
}
return this.putToWait(context, waitTill);
}
private async putToWait(context: IExecuteFunctions, waitTill: Date) {
await context.putExecutionToWait(waitTill);
return [context.getInputData()];
}
}