2024-02-14 07:12:23 -08:00
import { DateTime } from 'luxon' ;
2023-01-27 03:22:44 -08:00
import type {
2023-03-09 09:13:15 -08:00
IExecuteFunctions ,
2021-08-21 05:11:32 -07:00
INodeExecutionData ,
INodeTypeDescription ,
2023-07-04 07:17:50 -07:00
INodeProperties ,
IDisplayOptions ,
2023-12-13 07:00:51 -08:00
IWebhookFunctions ,
2021-08-21 05:11:32 -07:00
} from 'n8n-workflow' ;
2023-07-04 07:17:50 -07:00
import { WAIT_TIME_UNLIMITED } from 'n8n-workflow' ;
import {
authenticationProperty ,
credentialsProperty ,
defaultWebhookDescription ,
httpMethodsProperty ,
optionsProperty ,
responseBinaryPropertyNameProperty ,
responseCodeProperty ,
responseDataProperty ,
responseModeProperty ,
} from '../Webhook/description' ;
2023-12-13 07:00:51 -08:00
import {
formDescription ,
formFields ,
respondWithOptions ,
formRespondMode ,
formTitle ,
} from '../Form/common.descriptions' ;
import { formWebhook } from '../Form/utils' ;
import { updateDisplayOptions } from '../../utils/utilities' ;
2023-07-04 07:17:50 -07:00
import { Webhook } from '../Webhook/Webhook.node' ;
2024-03-01 05:14:56 -08:00
const toWaitAmount : INodeProperties = {
displayName : 'Wait Amount' ,
name : 'amount' ,
type : 'number' ,
typeOptions : {
minValue : 0 ,
numberPrecision : 2 ,
} ,
default : 1 ,
description : 'The time to wait' ,
} ;
const unitSelector : INodeProperties = {
displayName : 'Wait Unit' ,
name : 'unit' ,
type : 'options' ,
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' ,
} ;
2023-12-13 07:00:51 -08:00
const waitTimeProperties : INodeProperties [ ] = [
{
displayName : 'Limit Wait Time' ,
name : 'limitWaitTime' ,
type : 'boolean' ,
default : false ,
description :
'Whether the workflow will automatically resume execution after the specified limit type' ,
displayOptions : {
show : {
resume : [ 'webhook' , 'form' ] ,
} ,
} ,
} ,
{
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 ] ,
resume : [ 'webhook' , 'form' ] ,
} ,
} ,
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 ] ,
resume : [ 'webhook' , 'form' ] ,
} ,
} ,
typeOptions : {
minValue : 0 ,
numberPrecision : 2 ,
} ,
default : 1 ,
description : 'The time to wait' ,
} ,
{
displayName : 'Unit' ,
name : 'resumeUnit' ,
type : 'options' ,
displayOptions : {
show : {
limitType : [ 'afterTimeInterval' ] ,
limitWaitTime : [ true ] ,
resume : [ 'webhook' , 'form' ] ,
} ,
} ,
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 ] ,
resume : [ 'webhook' , 'form' ] ,
} ,
} ,
default : '' ,
description : 'Continue execution after the specified date and time' ,
} ,
] ;
const webhookSuffix : INodeProperties = {
displayName : 'Webhook Suffix' ,
name : 'webhookSuffix' ,
type : 'string' ,
default : '' ,
placeholder : 'webhook' ,
noDataExpression : true ,
description :
'This suffix path will be appended to the restart URL. Helpful when using multiple wait nodes.' ,
} ;
2023-07-04 07:17:50 -07:00
const displayOnWebhook : IDisplayOptions = {
show : {
resume : [ 'webhook' ] ,
} ,
} ;
2023-12-13 07:00:51 -08:00
const displayOnFormSubmission = {
show : {
resume : [ 'form' ] ,
} ,
} ;
const onFormSubmitProperties = updateDisplayOptions ( displayOnFormSubmission , [
formTitle ,
formDescription ,
formFields ,
formRespondMode ,
] ) ;
const onWebhookCallProperties = updateDisplayOptions ( displayOnWebhook , [
{
. . . httpMethodsProperty ,
description : 'The HTTP method of the Webhook call' ,
} ,
responseCodeProperty ,
responseModeProperty ,
responseDataProperty ,
responseBinaryPropertyNameProperty ,
] ) ;
const webhookPath = '={{$parameter["options"]["webhookSuffix"] || ""}}' ;
2023-07-04 07:17:50 -07:00
export class Wait extends Webhook {
authPropertyName = 'incomingAuthentication' ;
2021-08-21 05:11:32 -07:00
description : INodeTypeDescription = {
displayName : 'Wait' ,
name : 'wait' ,
icon : 'fa:pause-circle' ,
group : [ 'organization' ] ,
2024-03-01 05:14:56 -08:00
version : [ 1 , 1.1 ] ,
2021-08-21 05:11:32 -07:00
description : 'Wait before continue with execution' ,
defaults : {
name : 'Wait' ,
color : '#804050' ,
} ,
inputs : [ 'main' ] ,
outputs : [ 'main' ] ,
2023-07-04 07:17:50 -07:00
credentials : credentialsProperty ( this . authPropertyName ) ,
2021-08-21 05:11:32 -07:00
webhooks : [
{
2023-07-04 07:17:50 -07:00
. . . defaultWebhookDescription ,
2021-08-21 05:11:32 -07:00
responseData : '={{$parameter["responseData"]}}' ,
2023-12-13 07:00:51 -08:00
path : webhookPath ,
restartWebhook : true ,
} ,
{
name : 'default' ,
httpMethod : 'GET' ,
responseMode : 'onReceived' ,
path : webhookPath ,
2021-08-21 05:11:32 -07:00
restartWebhook : true ,
2023-12-13 07:00:51 -08:00
isFullPath : true ,
isForm : true ,
} ,
{
name : 'default' ,
httpMethod : 'POST' ,
responseMode : '={{$parameter["responseMode"]}}' ,
responseData : '={{$parameter["responseMode"] === "lastNode" ? "noData" : undefined}}' ,
path : webhookPath ,
restartWebhook : true ,
isFullPath : true ,
isForm : true ,
2021-08-21 05:11:32 -07:00
} ,
] ,
properties : [
{
displayName : 'Resume' ,
name : 'resume' ,
type : 'options' ,
options : [
{
2022-06-03 10:23:49 -07:00
name : 'After Time Interval' ,
2021-08-21 05:11:32 -07:00
value : 'timeInterval' ,
description : 'Waits for a certain amount of time' ,
} ,
{
2022-06-03 10:23:49 -07:00
name : 'At Specified Time' ,
2021-08-21 05:11:32 -07:00
value : 'specificTime' ,
2021-08-26 08:27:19 -07:00
description : 'Waits until a specific date and time to continue' ,
2021-08-21 05:11:32 -07:00
} ,
{
2022-06-03 10:23:49 -07:00
name : 'On Webhook Call' ,
2021-08-21 05:11:32 -07:00
value : 'webhook' ,
2021-08-26 08:27:19 -07:00
description : 'Waits for a webhook call before continuing' ,
2021-08-21 05:11:32 -07:00
} ,
2023-12-13 07:00:51 -08:00
{
name : 'On Form Submited' ,
value : 'form' ,
description : 'Waits for a form submission before continuing' ,
} ,
2021-08-21 05:11:32 -07:00
] ,
default : 'timeInterval' ,
2021-08-26 08:27:19 -07:00
description : 'Determines the waiting mode to use before the workflow continues' ,
2021-08-21 05:11:32 -07:00
} ,
2023-07-04 07:17:50 -07:00
{
. . . authenticationProperty ( this . authPropertyName ) ,
description :
'If and how incoming resume-webhook-requests to $execution.resumeUrl should be authenticated for additional security' ,
displayOptions : displayOnWebhook ,
} ,
2021-08-21 05:11:32 -07:00
// ----------------------------------
// resume:specificTime
// ----------------------------------
{
displayName : 'Date and Time' ,
name : 'dateTime' ,
type : 'dateTime' ,
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
resume : [ 'specificTime' ] ,
2021-08-21 05:11:32 -07:00
} ,
} ,
default : '' ,
description : 'The date and time to wait for before continuing' ,
} ,
// ----------------------------------
// resume:timeInterval
// ----------------------------------
{
2024-03-01 05:14:56 -08:00
. . . toWaitAmount ,
2021-08-21 05:11:32 -07:00
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
resume : [ 'timeInterval' ] ,
2024-03-01 05:14:56 -08:00
'@version' : [ 1 ] ,
2021-08-21 05:11:32 -07:00
} ,
} ,
} ,
{
2024-03-01 05:14:56 -08:00
. . . toWaitAmount ,
default : 5 ,
2021-08-21 05:11:32 -07:00
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
resume : [ 'timeInterval' ] ,
2021-08-21 05:11:32 -07:00
} ,
2024-03-01 05:14:56 -08:00
hide : {
'@version' : [ 1 ] ,
2021-08-21 05:11:32 -07:00
} ,
2024-03-01 05:14:56 -08:00
} ,
} ,
{
. . . unitSelector ,
displayOptions : {
show : {
resume : [ 'timeInterval' ] ,
'@version' : [ 1 ] ,
2021-08-21 05:11:32 -07:00
} ,
2024-03-01 05:14:56 -08:00
} ,
} ,
{
. . . unitSelector ,
default : 'seconds' ,
displayOptions : {
show : {
resume : [ 'timeInterval' ] ,
2021-08-21 05:11:32 -07:00
} ,
2024-03-01 05:14:56 -08:00
hide : {
'@version' : [ 1 ] ,
2021-08-21 05:11:32 -07:00
} ,
2024-03-01 05:14:56 -08:00
} ,
2021-08-21 05:11:32 -07:00
} ,
// ----------------------------------
2023-12-13 07:00:51 -08:00
// resume:webhook & form
2021-08-21 05:11:32 -07:00
// ----------------------------------
{
2022-08-17 08:50:24 -07:00
displayName :
2022-09-29 14:02:25 -07:00
'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>' ,
2021-08-21 05:11:32 -07:00
name : 'webhookNotice' ,
type : 'notice' ,
2023-07-04 07:17:50 -07:00
displayOptions : displayOnWebhook ,
2021-08-21 05:11:32 -07:00
default : '' ,
} ,
{
2023-12-13 07:00:51 -08:00
displayName :
'The form url will be generated at run time. It can be referenced with the <strong>$execution.resumeFormUrl</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 : 'formNotice' ,
type : 'notice' ,
displayOptions : displayOnFormSubmission ,
default : '' ,
2021-08-21 05:11:32 -07:00
} ,
2023-12-13 07:00:51 -08:00
. . . onFormSubmitProperties ,
. . . onWebhookCallProperties ,
. . . waitTimeProperties ,
2021-08-21 05:11:32 -07:00
{
2023-12-13 07:00:51 -08:00
. . . optionsProperty ,
2023-07-04 07:17:50 -07:00
displayOptions : displayOnWebhook ,
2023-12-13 07:00:51 -08:00
options : [ . . . ( optionsProperty . options as INodeProperties [ ] ) , webhookSuffix ] ,
2021-08-21 05:11:32 -07:00
} ,
{
2023-12-13 07:00:51 -08:00
displayName : 'Options' ,
name : 'options' ,
type : 'collection' ,
placeholder : 'Add Option' ,
default : { } ,
2021-08-21 05:11:32 -07:00
displayOptions : {
show : {
2023-12-13 07:00:51 -08:00
resume : [ 'form' ] ,
2021-08-21 05:11:32 -07:00
} ,
2023-12-13 07:00:51 -08:00
hide : {
responseMode : [ 'responseNode' ] ,
2021-08-21 05:11:32 -07:00
} ,
} ,
2023-12-13 07:00:51 -08:00
options : [ respondWithOptions , webhookSuffix ] ,
2021-08-21 05:11:32 -07:00
} ,
{
2023-12-13 07:00:51 -08:00
displayName : 'Options' ,
name : 'options' ,
type : 'collection' ,
placeholder : 'Add Option' ,
default : { } ,
2021-08-21 05:11:32 -07:00
displayOptions : {
show : {
2023-12-13 07:00:51 -08:00
resume : [ 'form' ] ,
2021-08-21 05:11:32 -07:00
} ,
2023-12-13 07:00:51 -08:00
hide : {
responseMode : [ 'onReceived' , 'lastNode' ] ,
2021-08-21 05:11:32 -07:00
} ,
} ,
2023-12-13 07:00:51 -08:00
options : [ webhookSuffix ] ,
2021-08-21 05:11:32 -07:00
} ,
] ,
} ;
2023-12-13 07:00:51 -08:00
async webhook ( context : IWebhookFunctions ) {
const resume = context . getNodeParameter ( 'resume' , 0 ) as string ;
2024-01-17 07:08:50 -08:00
if ( resume === 'form' ) return await formWebhook ( context ) ;
return await super . webhook ( context ) ;
2023-12-13 07:00:51 -08:00
}
2023-07-04 07:17:50 -07:00
async execute ( context : IExecuteFunctions ) : Promise < INodeExecutionData [ ] [ ] > {
const resume = context . getNodeParameter ( 'resume' , 0 ) as string ;
2021-08-21 05:11:32 -07:00
2023-12-13 07:00:51 -08:00
if ( [ 'webhook' , 'form' ] . includes ( resume ) ) {
2024-01-17 07:08:50 -08:00
return await this . configureAndPutToWait ( context ) ;
2021-08-21 05:11:32 -07:00
}
let waitTill : Date ;
if ( resume === 'timeInterval' ) {
2023-07-04 07:17:50 -07:00
const unit = context . getNodeParameter ( 'unit' , 0 ) as string ;
2021-08-21 05:11:32 -07:00
2023-07-04 07:17:50 -07:00
let waitAmount = context . getNodeParameter ( 'amount' , 0 ) as number ;
2021-08-21 05:11:32 -07:00
if ( unit === 'minutes' ) {
waitAmount *= 60 ;
}
if ( unit === 'hours' ) {
waitAmount *= 60 * 60 ;
}
if ( unit === 'days' ) {
waitAmount *= 60 * 60 * 24 ;
}
waitAmount *= 1000 ;
2024-02-14 07:12:23 -08:00
// Timezone does not change relative dates, since they are just
// a number of seconds added to the current timestamp
2021-08-21 05:11:32 -07:00
waitTill = new Date ( new Date ( ) . getTime ( ) + waitAmount ) ;
} else {
2024-02-14 07:12:23 -08:00
const dateTimeStr = context . getNodeParameter ( 'dateTime' , 0 ) as string ;
2021-08-21 05:11:32 -07:00
2024-02-14 07:12:23 -08:00
waitTill = DateTime . fromFormat ( dateTimeStr , "yyyy-MM-dd'T'HH:mm:ss" , {
zone : context.getTimezone ( ) ,
} )
. toUTC ( )
. toJSDate ( ) ;
2021-08-21 05:11:32 -07:00
}
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.
2024-01-17 07:08:50 -08:00
return await new Promise ( ( resolve ) = > {
2023-11-24 09:17:06 -08:00
const timer = setTimeout ( ( ) = > resolve ( [ context . getInputData ( ) ] ) , waitValue ) ;
context . onExecutionCancellation ( ( ) = > clearTimeout ( timer ) ) ;
2021-08-21 05:11:32 -07:00
} ) ;
}
2023-07-04 07:17:50 -07:00
// If longer than 65 seconds put execution to wait
2024-01-17 07:08:50 -08:00
return await this . putToWait ( context , waitTill ) ;
2023-07-04 07:17:50 -07:00
}
2023-12-13 07:00:51 -08:00
private async configureAndPutToWait ( context : IExecuteFunctions ) {
2023-07-04 07:17:50 -07:00
let waitTill = new Date ( WAIT_TIME_UNLIMITED ) ;
const limitWaitTime = context . getNodeParameter ( 'limitWaitTime' , 0 ) ;
if ( limitWaitTime === true ) {
const limitType = context . getNodeParameter ( 'limitType' , 0 ) ;
2023-12-13 07:00:51 -08:00
2023-07-04 07:17:50 -07:00
if ( limitType === 'afterTimeInterval' ) {
let waitAmount = context . getNodeParameter ( 'resumeAmount' , 0 ) as number ;
const resumeUnit = context . getNodeParameter ( 'resumeUnit' , 0 ) ;
2023-12-13 07:00:51 -08:00
2023-07-04 07:17:50 -07:00
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 ) ;
}
}
2024-01-17 07:08:50 -08:00
return await this . putToWait ( context , waitTill ) ;
2023-07-04 07:17:50 -07:00
}
2021-08-21 05:11:32 -07:00
2023-07-04 07:17:50 -07:00
private async putToWait ( context : IExecuteFunctions , waitTill : Date ) {
await context . putExecutionToWait ( waitTill ) ;
return [ context . getInputData ( ) ] ;
2021-08-21 05:11:32 -07:00
}
}