From 09f713a115f55a9aa6b69a9a4a42f707e807ff06 Mon Sep 17 00:00:00 2001 From: Michael Kret Date: Tue, 2 Apr 2024 12:31:07 +0300 Subject: [PATCH 1/8] :zap: multiple paths parameter --- .../nodes-base/nodes/Webhook/Webhook.node.ts | 52 ++++++++++++++++-- packages/nodes-base/nodes/Webhook/utils.ts | 53 ++++++++++++++++--- 2 files changed, 95 insertions(+), 10 deletions(-) diff --git a/packages/nodes-base/nodes/Webhook/Webhook.node.ts b/packages/nodes-base/nodes/Webhook/Webhook.node.ts index 90d832ec5b30d..7372117fed3b0 100644 --- a/packages/nodes-base/nodes/Webhook/Webhook.node.ts +++ b/packages/nodes-base/nodes/Webhook/Webhook.node.ts @@ -49,7 +49,7 @@ export class Webhook extends Node { icon: 'file:webhook.svg', name: 'webhook', group: ['trigger'], - version: [1, 1.1, 2], + version: [1, 1.1, 2, 2.1], description: 'Starts the workflow when a webhook is called', eventTriggerDescription: 'Waiting for you to call the Test URL', activationMessage: 'You can now make calls to your production webhook URL.', @@ -74,7 +74,52 @@ export class Webhook extends Node { credentials: credentialsProperty(this.authPropertyName), webhooks: [defaultWebhookDescription], properties: [ - httpMethodsProperty, + { + ...httpMethodsProperty, + displayOptions: { + show: { + '@version': [1, 1.1, 2], + }, + }, + }, + { + displayName: 'HTTP Methods', + name: 'httpMethod', + type: 'multiOptions', + options: [ + { + name: 'DELETE', + value: 'DELETE', + }, + { + name: 'GET', + value: 'GET', + }, + { + name: 'HEAD', + value: 'HEAD', + }, + { + name: 'PATCH', + value: 'PATCH', + }, + { + name: 'POST', + value: 'POST', + }, + { + name: 'PUT', + value: 'PUT', + }, + ], + default: ['GET', 'POST'], + description: 'The HTTP methods to listen to', + displayOptions: { + show: { + '@version': [{ _cnd: { gte: 2.1 } }], + }, + }, + }, { displayName: 'Path', name: 'path', @@ -144,6 +189,7 @@ export class Webhook extends Node { }; const req = context.getRequestObject(); const resp = context.getResponseObject(); + const requestMethod = context.getRequestObject().method; if (!isIpWhitelisted(options.ipWhitelist, req.ips, req.ip)) { resp.writeHead(403); @@ -165,7 +211,7 @@ export class Webhook extends Node { throw error; } - const prepareOutput = setupOutputConnection(context, { + const prepareOutput = setupOutputConnection(context, requestMethod, { jwtPayload: validationData, }); diff --git a/packages/nodes-base/nodes/Webhook/utils.ts b/packages/nodes-base/nodes/Webhook/utils.ts index 1bf6b8407e451..f6a68e8e24661 100644 --- a/packages/nodes-base/nodes/Webhook/utils.ts +++ b/packages/nodes-base/nodes/Webhook/utils.ts @@ -49,23 +49,46 @@ export const getResponseData = (parameters: WebhookParameters) => { return undefined; }; +// export const configuredOutputs = (parameters: WebhookParameters) => { +// const httpMethod = parameters.httpMethod; + +// return [ +// { +// type: `${NodeConnectionType.Main}`, +// displayName: httpMethod, +// }, +// ]; +// }; + export const configuredOutputs = (parameters: WebhookParameters) => { - const httpMethod = parameters.httpMethod; + const httpMethod = parameters.httpMethod as string | string[]; + + if (!Array.isArray(httpMethod)) + return [ + { + type: `${NodeConnectionType.Main}`, + displayName: httpMethod, + }, + ]; - return [ - { + const outputs = httpMethod.map((method) => { + return { type: `${NodeConnectionType.Main}`, - displayName: httpMethod, - }, - ]; + displayName: method, + }; + }); + + return outputs; }; export const setupOutputConnection = ( ctx: IWebhookFunctions, + method: string, additionalData: { jwtPayload?: IDataObject; }, ) => { + const httpMethod = ctx.getNodeParameter('httpMethod', []) as string[] | string; let webhookUrl = ctx.getNodeWebhookUrl('default') as string; const executionMode = ctx.getMode() === 'manual' ? 'test' : 'production'; @@ -73,13 +96,29 @@ export const setupOutputConnection = ( webhookUrl = webhookUrl.replace('/webhook/', '/webhook-test/'); } + // before version 2.1, httpMethod was a string and not an array + if (!Array.isArray(httpMethod)) { + return (outputData: INodeExecutionData): INodeExecutionData[][] => { + outputData.json.webhookUrl = webhookUrl; + outputData.json.executionMode = executionMode; + if (additionalData?.jwtPayload) { + outputData.json.jwtPayload = additionalData.jwtPayload; + } + return [[outputData]]; + }; + } + + const outputIndex = httpMethod.indexOf(method.toUpperCase()); + const outputs: INodeExecutionData[][] = httpMethod.map(() => []); + return (outputData: INodeExecutionData): INodeExecutionData[][] => { outputData.json.webhookUrl = webhookUrl; outputData.json.executionMode = executionMode; if (additionalData?.jwtPayload) { outputData.json.jwtPayload = additionalData.jwtPayload; } - return [[outputData]]; + outputs[outputIndex] = [outputData]; + return outputs; }; }; From 944889cd93e7ce64cc138783f4be19116c5a0d4f Mon Sep 17 00:00:00 2001 From: Michael Kret Date: Mon, 8 Apr 2024 15:59:06 +0300 Subject: [PATCH 2/8] :zap: multi method webhook parameter --- .../editor-ui/src/components/NodeWebhooks.vue | 19 ++++++++++--- .../src/composables/useWorkflowHelpers.ts | 16 ++++++++--- packages/workflow/src/NodeHelpers.ts | 27 +++++++++++-------- 3 files changed, 45 insertions(+), 17 deletions(-) diff --git a/packages/editor-ui/src/components/NodeWebhooks.vue b/packages/editor-ui/src/components/NodeWebhooks.vue index 7f107f81dd135..5ee9d9979fb0a 100644 --- a/packages/editor-ui/src/components/NodeWebhooks.vue +++ b/packages/editor-ui/src/components/NodeWebhooks.vue @@ -28,9 +28,7 @@ >
-
- {{ workflowHelpers.getWebhookExpressionValue(webhook, 'httpMethod') }}
-
+
{{ getWebhookHttpMethod(webhook) }}
@@ -195,12 +193,27 @@ export default defineComponent({ return ''; }, isWebhookMethodVisible(webhook: IWebhookDescription): boolean { + try { + const method = this.workflowHelpers.getWebhookExpressionValue(webhook, 'httpMethod', false); + if (Array.isArray(method) && method.length !== 1) { + return false; + } + } catch (error) {} + if (typeof webhook.ndvHideMethod === 'string') { return !this.workflowHelpers.getWebhookExpressionValue(webhook, 'ndvHideMethod'); } return !webhook.ndvHideMethod; }, + + getWebhookHttpMethod(webhook: IWebhookDescription): string { + const method = this.workflowHelpers.getWebhookExpressionValue(webhook, 'httpMethod', false); + if (Array.isArray(method)) { + return method[0]; + } + return method; + }, }, }); diff --git a/packages/editor-ui/src/composables/useWorkflowHelpers.ts b/packages/editor-ui/src/composables/useWorkflowHelpers.ts index 1e26d6d8a8144..e6b452abbcbb0 100644 --- a/packages/editor-ui/src/composables/useWorkflowHelpers.ts +++ b/packages/editor-ui/src/composables/useWorkflowHelpers.ts @@ -738,12 +738,21 @@ export function useWorkflowHelpers(options: { router: ReturnType unknown } | null; if (proxy?.isProxy && proxy.toJSON) return JSON.stringify(proxy.toJSON()); const workflow = getCurrentWorkflow(); diff --git a/packages/workflow/src/NodeHelpers.ts b/packages/workflow/src/NodeHelpers.ts index 101dd20e1b580..0845e716e6b68 100644 --- a/packages/workflow/src/NodeHelpers.ts +++ b/packages/workflow/src/NodeHelpers.ts @@ -995,7 +995,7 @@ export function getNodeWebhooks( ) as boolean; const path = getNodeWebhookPath(workflowId, node, nodeWebhookPath, isFullPath, restartWebhook); - const httpMethod = workflow.expression.getSimpleParameterValue( + const webhookMethods = workflow.expression.getSimpleParameterValue( node, webhookDescription.httpMethod, mode, @@ -1004,7 +1004,7 @@ export function getNodeWebhooks( 'GET', ); - if (httpMethod === undefined) { + if (webhookMethods === undefined) { // TODO: Use a proper logger console.error( `The webhook "${path}" for node "${node.name}" in workflow "${workflowId}" could not be added because the httpMethod is not defined.`, @@ -1017,15 +1017,20 @@ export function getNodeWebhooks( webhookId = node.webhookId; } - returnData.push({ - httpMethod: httpMethod.toString() as IHttpRequestMethods, - node: node.name, - path, - webhookDescription, - workflowId, - workflowExecuteAdditionalData: additionalData, - webhookId, - }); + String(webhookMethods) + .split(',') + .forEach((httpMethod) => { + if (!httpMethod) return; + returnData.push({ + httpMethod: httpMethod.trim() as IHttpRequestMethods, + node: node.name, + path, + webhookDescription, + workflowId, + workflowExecuteAdditionalData: additionalData, + webhookId, + }); + }); } return returnData; From f638bee7d6888677fa7ee7badea00c462ad5b674 Mon Sep 17 00:00:00 2001 From: Michael Kret Date: Mon, 8 Apr 2024 16:30:41 +0300 Subject: [PATCH 3/8] :zap: setting for multiple webhooks --- packages/nodes-base/nodes/Webhook/Webhook.node.ts | 13 +++++++++++-- packages/nodes-base/nodes/Webhook/utils.ts | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/nodes-base/nodes/Webhook/Webhook.node.ts b/packages/nodes-base/nodes/Webhook/Webhook.node.ts index 7372117fed3b0..87ce408fb2d72 100644 --- a/packages/nodes-base/nodes/Webhook/Webhook.node.ts +++ b/packages/nodes-base/nodes/Webhook/Webhook.node.ts @@ -74,11 +74,20 @@ export class Webhook extends Node { credentials: credentialsProperty(this.authPropertyName), webhooks: [defaultWebhookDescription], properties: [ + { + displayName: 'Multiple Methods', + name: 'multipleMethods', + type: 'boolean', + default: false, + isNodeSetting: true, + description: + 'Whether multiple methods could be used at the same time to listen to the webhook', + }, { ...httpMethodsProperty, displayOptions: { show: { - '@version': [1, 1.1, 2], + multipleMethods: [false], }, }, }, @@ -116,7 +125,7 @@ export class Webhook extends Node { description: 'The HTTP methods to listen to', displayOptions: { show: { - '@version': [{ _cnd: { gte: 2.1 } }], + multipleMethods: [true], }, }, }, diff --git a/packages/nodes-base/nodes/Webhook/utils.ts b/packages/nodes-base/nodes/Webhook/utils.ts index f6a68e8e24661..1312dbb98375f 100644 --- a/packages/nodes-base/nodes/Webhook/utils.ts +++ b/packages/nodes-base/nodes/Webhook/utils.ts @@ -96,7 +96,7 @@ export const setupOutputConnection = ( webhookUrl = webhookUrl.replace('/webhook/', '/webhook-test/'); } - // before version 2.1, httpMethod was a string and not an array + // multi methods could be set in settings of node, so we need to check if it's an array if (!Array.isArray(httpMethod)) { return (outputData: INodeExecutionData): INodeExecutionData[][] => { outputData.json.webhookUrl = webhookUrl; From d0c6321926d0ff1d92185f66c317c9fca430ed01 Mon Sep 17 00:00:00 2001 From: Michael Kret Date: Mon, 8 Apr 2024 16:38:05 +0300 Subject: [PATCH 4/8] :zap: clean up --- packages/nodes-base/nodes/Webhook/Webhook.node.ts | 2 +- packages/nodes-base/nodes/Webhook/utils.ts | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/packages/nodes-base/nodes/Webhook/Webhook.node.ts b/packages/nodes-base/nodes/Webhook/Webhook.node.ts index 87ce408fb2d72..d203daba14b1a 100644 --- a/packages/nodes-base/nodes/Webhook/Webhook.node.ts +++ b/packages/nodes-base/nodes/Webhook/Webhook.node.ts @@ -49,7 +49,7 @@ export class Webhook extends Node { icon: 'file:webhook.svg', name: 'webhook', group: ['trigger'], - version: [1, 1.1, 2, 2.1], + version: [1, 1.1, 2], description: 'Starts the workflow when a webhook is called', eventTriggerDescription: 'Waiting for you to call the Test URL', activationMessage: 'You can now make calls to your production webhook URL.', diff --git a/packages/nodes-base/nodes/Webhook/utils.ts b/packages/nodes-base/nodes/Webhook/utils.ts index 1312dbb98375f..33fd36331ecc8 100644 --- a/packages/nodes-base/nodes/Webhook/utils.ts +++ b/packages/nodes-base/nodes/Webhook/utils.ts @@ -49,17 +49,6 @@ export const getResponseData = (parameters: WebhookParameters) => { return undefined; }; -// export const configuredOutputs = (parameters: WebhookParameters) => { -// const httpMethod = parameters.httpMethod; - -// return [ -// { -// type: `${NodeConnectionType.Main}`, -// displayName: httpMethod, -// }, -// ]; -// }; - export const configuredOutputs = (parameters: WebhookParameters) => { const httpMethod = parameters.httpMethod as string | string[]; From a485fe8a3b3eb5655d5d74f09f7682cecbe3a5ac Mon Sep 17 00:00:00 2001 From: Giulio Andreini Date: Wed, 10 Apr 2024 16:34:45 +0200 Subject: [PATCH 5/8] Minor copy tweaks. --- packages/nodes-base/nodes/Webhook/Webhook.node.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/nodes/Webhook/Webhook.node.ts b/packages/nodes-base/nodes/Webhook/Webhook.node.ts index d203daba14b1a..fa2aadd2438ca 100644 --- a/packages/nodes-base/nodes/Webhook/Webhook.node.ts +++ b/packages/nodes-base/nodes/Webhook/Webhook.node.ts @@ -75,13 +75,13 @@ export class Webhook extends Node { webhooks: [defaultWebhookDescription], properties: [ { - displayName: 'Multiple Methods', + displayName: 'Allow Multiple HTTP Methods', name: 'multipleMethods', type: 'boolean', default: false, isNodeSetting: true, description: - 'Whether multiple methods could be used at the same time to listen to the webhook', + 'Whether to allow the webhook to listen for multiple HTTP methods', }, { ...httpMethodsProperty, From a0176d4ec252c8400b09d659513ade7225a8df1f Mon Sep 17 00:00:00 2001 From: Michael Kret Date: Tue, 16 Apr 2024 09:35:23 +0300 Subject: [PATCH 6/8] :zap: lint fix --- packages/nodes-base/nodes/Webhook/Webhook.node.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/nodes-base/nodes/Webhook/Webhook.node.ts b/packages/nodes-base/nodes/Webhook/Webhook.node.ts index 1251694f848fc..98b699962ac04 100644 --- a/packages/nodes-base/nodes/Webhook/Webhook.node.ts +++ b/packages/nodes-base/nodes/Webhook/Webhook.node.ts @@ -80,8 +80,7 @@ export class Webhook extends Node { type: 'boolean', default: false, isNodeSetting: true, - description: - 'Whether to allow the webhook to listen for multiple HTTP methods', + description: 'Whether to allow the webhook to listen for multiple HTTP methods', }, { ...httpMethodsProperty, From 8bd13b723a865aeeabad94cc7bb4286b7b1f58f8 Mon Sep 17 00:00:00 2001 From: Michael Kret Date: Tue, 16 Apr 2024 10:39:26 +0300 Subject: [PATCH 7/8] :zap: methods tooltip display fix --- packages/editor-ui/src/components/TriggerPanel.vue | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/editor-ui/src/components/TriggerPanel.vue b/packages/editor-ui/src/components/TriggerPanel.vue index 8c022a924fbd5..c53ecfc0deda1 100644 --- a/packages/editor-ui/src/components/TriggerPanel.vue +++ b/packages/editor-ui/src/components/TriggerPanel.vue @@ -225,10 +225,17 @@ export default defineComponent({ return undefined; } - return this.workflowHelpers.getWebhookExpressionValue( + const httpMethod = this.workflowHelpers.getWebhookExpressionValue( this.nodeType.webhooks[0], 'httpMethod', + false, ); + + if (Array.isArray(httpMethod)) { + return httpMethod.join(', '); + } + + return httpMethod; }, webhookTestUrl(): string | undefined { if (!this.node || !this.nodeType?.webhooks?.length) { From 6254df3c6f2399a58a889d16a04fd4d88e8ac638 Mon Sep 17 00:00:00 2001 From: Michael Kret Date: Tue, 16 Apr 2024 12:18:35 +0300 Subject: [PATCH 8/8] :zap: prevent webhook to throw registration error, after reloading page with test webhook listening --- packages/cli/src/TestWebhooks.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/TestWebhooks.ts b/packages/cli/src/TestWebhooks.ts index d3db5357a4308..827226ee546be 100644 --- a/packages/cli/src/TestWebhooks.ts +++ b/packages/cli/src/TestWebhooks.ts @@ -238,13 +238,20 @@ export class TestWebhooks implements IWebhookManager { for (const webhook of webhooks) { const key = this.registrations.toKey(webhook); - const isAlreadyRegistered = await this.registrations.get(key); + const registrationByKey = await this.registrations.get(key); if (runData && webhook.node in runData) { return false; } - if (isAlreadyRegistered && !webhook.webhookId) { + // if registration already exists and is not a test webhook created by this user in this workflow throw an error + if ( + registrationByKey && + !webhook.webhookId && + !registrationByKey.webhook.isTest && + registrationByKey.webhook.userId !== userId && + registrationByKey.webhook.workflowId !== workflow.id + ) { throw new WebhookPathTakenError(webhook.node); }