UNPKG

n8n-nodes-base

Version:

Base nodes of n8n

535 lines 20.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Wait = void 0; const n8n_workflow_1 = require("n8n-workflow"); const validation_1 = require("./validation"); const utilities_1 = require("../../utils/utilities"); const common_descriptions_1 = require("../Form/common.descriptions"); const utils_1 = require("../Form/utils/utils"); const description_1 = require("../Webhook/description"); const Webhook_node_1 = require("../Webhook/Webhook.node"); const toWaitAmount = { displayName: 'Wait Amount', name: 'amount', type: 'number', typeOptions: { minValue: 0, numberPrecision: 2, }, default: 1, description: 'The time to wait', validateType: 'number', }; const unitSelector = { 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', }; const waitTimeProperties = [ { displayName: 'Limit Wait Time', name: 'limitWaitTime', type: 'boolean', default: false, description: 'Whether to limit the time this node should wait for a user response before execution resumes', 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 = { 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.', }; const displayOnWebhook = { show: { resume: ['webhook'], }, }; const displayOnFormSubmission = { show: { resume: ['form'], }, }; const onFormSubmitProperties = (0, utilities_1.updateDisplayOptions)(displayOnFormSubmission, [ common_descriptions_1.formTitle, common_descriptions_1.formDescription, common_descriptions_1.formFields, common_descriptions_1.formRespondMode, ]); const onWebhookCallProperties = (0, utilities_1.updateDisplayOptions)(displayOnWebhook, [ { ...description_1.httpMethodsProperty, description: 'The HTTP method of the Webhook call', }, description_1.responseCodeProperty, description_1.responseModeProperty, description_1.responseDataProperty, description_1.responseBinaryPropertyNameProperty, ]); const webhookPath = '={{$parameter["options"]["webhookSuffix"] || ""}}'; const waitingTooltip = (parameters, resumeUrl, formResumeUrl) => { const resume = parameters.resume; if (['webhook', 'form'].includes(resume)) { const { webhookSuffix } = (parameters.options ?? {}); const suffix = webhookSuffix && typeof webhookSuffix !== 'object' ? `/${webhookSuffix}` : ''; let message = ''; const url = `${resume === 'form' ? formResumeUrl : resumeUrl}${suffix}`; if (resume === 'form') { message = 'Execution will continue when form is submitted on '; } if (resume === 'webhook') { message = 'Execution will continue when webhook is received on '; } return `${message}<a href="${url}" target="_blank">${url}</a>`; } return 'Execution will continue when wait time is over'; }; class Wait extends Webhook_node_1.Webhook { authPropertyName = 'incomingAuthentication'; description = { displayName: 'Wait', name: 'wait', icon: 'fa:pause-circle', iconColor: 'crimson', group: ['organization'], version: [1, 1.1], description: 'Wait before continue with execution', defaults: { name: 'Wait', color: '#804050', }, inputs: [n8n_workflow_1.NodeConnectionTypes.Main], outputs: [n8n_workflow_1.NodeConnectionTypes.Main], credentials: (0, description_1.credentialsProperty)(this.authPropertyName), waitingNodeTooltip: `={{ (${waitingTooltip})($parameter, $execution.resumeUrl, $execution.resumeFormUrl) }}`, webhooks: [ { ...description_1.defaultWebhookDescription, responseData: '={{$parameter["responseData"]}}', path: webhookPath, restartWebhook: true, }, { name: 'default', httpMethod: 'GET', responseMode: 'onReceived', path: webhookPath, restartWebhook: true, isFullPath: true, nodeType: 'form', }, { name: 'default', httpMethod: 'POST', responseMode: '={{$parameter["responseMode"]}}', responseData: '={{$parameter["responseMode"] === "lastNode" ? "noData" : undefined}}', path: webhookPath, restartWebhook: true, isFullPath: true, nodeType: 'form', }, ], properties: [ { displayName: 'Resume', name: 'resume', type: 'options', builderHint: { message: 'For user approval workflows, consider using nodes with operation: "sendAndWait" (e.g., email, Slack) instead of Wait node. If using "webhook", the URL will be generated at runtime and can be referenced with {{ $execution.resumeUrl }}.', }, 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', }, { name: 'On Form Submitted', value: 'form', description: 'Waits for a form submission before continuing', }, ], default: 'timeInterval', description: 'Determines the waiting mode to use before the workflow continues', }, { displayName: 'Authentication', name: 'incomingAuthentication', type: 'options', options: [ { name: 'Basic Auth', value: 'basicAuth', }, { name: 'None', value: 'none', }, ], default: 'none', description: 'If and how incoming resume-webhook-requests to $execution.resumeFormUrl should be authenticated for additional security', displayOptions: { show: { resume: ['form'], }, }, }, { ...(0, description_1.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', required: true, }, // ---------------------------------- // resume:timeInterval // ---------------------------------- { ...toWaitAmount, displayOptions: { show: { resume: ['timeInterval'], '@version': [1], }, }, }, { ...toWaitAmount, default: 5, displayOptions: { show: { resume: ['timeInterval'], }, hide: { '@version': [1], }, }, }, { ...unitSelector, displayOptions: { show: { resume: ['timeInterval'], '@version': [1], }, }, }, { ...unitSelector, default: 'seconds', displayOptions: { show: { resume: ['timeInterval'], }, hide: { '@version': [1], }, }, }, // ---------------------------------- // resume:webhook & form // ---------------------------------- { 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: '', }, { 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: '', }, ...onFormSubmitProperties, ...onWebhookCallProperties, ...waitTimeProperties, { ...description_1.optionsProperty, displayOptions: displayOnWebhook, options: [...description_1.optionsProperty.options, webhookSuffix], }, { displayName: 'Options', name: 'options', type: 'collection', placeholder: 'Add option', default: {}, displayOptions: { show: { resume: ['form'], }, hide: { responseMode: ['responseNode'], }, }, options: [common_descriptions_1.appendAttributionToForm, common_descriptions_1.respondWithOptions, webhookSuffix], }, { displayName: 'Options', name: 'options', type: 'collection', placeholder: 'Add option', default: {}, displayOptions: { show: { resume: ['form'], }, hide: { responseMode: ['onReceived', 'lastNode'], }, }, options: [common_descriptions_1.appendAttributionToForm, webhookSuffix], }, ], }; async webhook(context) { const resume = context.getNodeParameter('resume', 0); if (resume === 'form') return await (0, utils_1.formWebhook)(context, this.authPropertyName); return await super.webhook(context); } async execute(context) { const resume = context.getNodeParameter('resume', 0); if (['webhook', 'form'].includes(resume)) { let hasFormTrigger = false; if (resume === 'form') { const parentNodes = context.getParentNodes(context.getNode().name); hasFormTrigger = parentNodes.some((node) => node.type === n8n_workflow_1.FORM_TRIGGER_NODE_TYPE); } const returnData = await this.configureAndPutToWait(context); if (resume === 'form' && hasFormTrigger) { context.sendResponse({ headers: { location: context.evaluateExpression('{{ $execution.resumeFormUrl }}', 0), }, statusCode: 307, }); } return returnData; } let waitTill; if (resume === 'timeInterval') { const unit = context.getNodeParameter('unit', 0); if (!(0, validation_1.validateWaitUnit)(unit)) { throw new n8n_workflow_1.NodeOperationError(context.getNode(), "Invalid wait unit. Valid units are 'seconds', 'minutes', 'hours', or 'days'."); } let waitAmount = context.getNodeParameter('amount', 0); if (!(0, validation_1.validateWaitAmount)(waitAmount)) { throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'Invalid wait amount. Please enter a number that is 0 or greater.'); } if (unit === 'minutes') { waitAmount *= 60; } if (unit === 'hours') { waitAmount *= 60 * 60; } if (unit === 'days') { waitAmount *= 60 * 60 * 24; } waitAmount *= 1000; // Timezone does not change relative dates, since they are just // a number of seconds added to the current timestamp waitTill = new Date(new Date().getTime() + waitAmount); } else { try { const dateTimeStrRaw = context.getNodeParameter('dateTime', 0); const parsedDateTime = (0, n8n_workflow_1.tryToParseDateTime)(dateTimeStrRaw, context.getTimezone()); waitTill = parsedDateTime.toUTC().toJSDate(); } catch (e) { throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'Cannot put execution to wait because `dateTime` parameter is not a valid date. Please pick a specific date and time to wait until.'); } } 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 await new Promise((resolve, _reject) => { const timer = setTimeout(() => resolve([context.getInputData()]), waitValue); context.onExecutionCancellation(() => { clearTimeout(timer); resolve([context.getInputData()]); }); }); } // If longer than 65 seconds put execution to wait return await this.putToWait(context, waitTill); } async configureAndPutToWait(context) { let waitTill = n8n_workflow_1.WAIT_INDEFINITELY; const limitWaitTime = context.getNodeParameter('limitWaitTime', 0); if (limitWaitTime === true) { const limitType = context.getNodeParameter('limitType', 0); if (limitType === 'afterTimeInterval') { let waitAmount = context.getNodeParameter('resumeAmount', 0); 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)); } } return await this.putToWait(context, waitTill); } async putToWait(context, waitTill) { await context.putExecutionToWait(waitTill); return [context.getInputData()]; } } exports.Wait = Wait; //# sourceMappingURL=Wait.node.js.map