mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 20:24:05 -08:00
feat(Webhook Node): Setting to enable multiple outputs/methods (#9086)
Co-authored-by: Giulio Andreini <andreini@netseven.it>
This commit is contained in:
parent
f6142ff275
commit
2bf0a3933e
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -28,9 +28,7 @@
|
|||
>
|
||||
<div v-if="isWebhookMethodVisible(webhook)" class="webhook-wrapper">
|
||||
<div class="http-field">
|
||||
<div class="http-method">
|
||||
{{ workflowHelpers.getWebhookExpressionValue(webhook, 'httpMethod') }}<br />
|
||||
</div>
|
||||
<div class="http-method">{{ getWebhookHttpMethod(webhook) }}<br /></div>
|
||||
</div>
|
||||
<div class="url-field">
|
||||
<div class="webhook-url left-ellipsis clickable" @click="copyWebhookUrl(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;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -738,12 +738,21 @@ export function useWorkflowHelpers(options: { router: ReturnType<typeof useRoute
|
|||
return nodeData;
|
||||
}
|
||||
|
||||
function getWebhookExpressionValue(webhookData: IWebhookDescription, key: string): string {
|
||||
function getWebhookExpressionValue(
|
||||
webhookData: IWebhookDescription,
|
||||
key: string,
|
||||
stringify = true,
|
||||
): string {
|
||||
if (webhookData[key] === undefined) {
|
||||
return 'empty';
|
||||
}
|
||||
try {
|
||||
return resolveExpression(webhookData[key] as string) as string;
|
||||
return resolveExpression(
|
||||
webhookData[key] as string,
|
||||
undefined,
|
||||
undefined,
|
||||
stringify,
|
||||
) as string;
|
||||
} catch (e) {
|
||||
return i18n.baseText('nodeWebhooks.invalidExpression');
|
||||
}
|
||||
|
@ -785,6 +794,7 @@ export function useWorkflowHelpers(options: { router: ReturnType<typeof useRoute
|
|||
c?: number;
|
||||
additionalKeys?: IWorkflowDataProxyAdditionalKeys;
|
||||
} = {},
|
||||
stringifyObject = true,
|
||||
) {
|
||||
const parameters = {
|
||||
__xxxxxxx__: expression,
|
||||
|
@ -796,7 +806,7 @@ export function useWorkflowHelpers(options: { router: ReturnType<typeof useRoute
|
|||
}
|
||||
|
||||
const obj = returnData.__xxxxxxx__;
|
||||
if (typeof obj === 'object') {
|
||||
if (typeof obj === 'object' && stringifyObject) {
|
||||
const proxy = obj as { isProxy: boolean; toJSON?: () => unknown } | null;
|
||||
if (proxy?.isProxy && proxy.toJSON) return JSON.stringify(proxy.toJSON());
|
||||
const workflow = getCurrentWorkflow();
|
||||
|
|
|
@ -74,7 +74,60 @@ export class Webhook extends Node {
|
|||
credentials: credentialsProperty(this.authPropertyName),
|
||||
webhooks: [defaultWebhookDescription],
|
||||
properties: [
|
||||
httpMethodsProperty,
|
||||
{
|
||||
displayName: 'Allow Multiple HTTP Methods',
|
||||
name: 'multipleMethods',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
isNodeSetting: true,
|
||||
description: 'Whether to allow the webhook to listen for multiple HTTP methods',
|
||||
},
|
||||
{
|
||||
...httpMethodsProperty,
|
||||
displayOptions: {
|
||||
show: {
|
||||
multipleMethods: [false],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
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: {
|
||||
multipleMethods: [true],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Path',
|
||||
name: 'path',
|
||||
|
@ -144,6 +197,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 +219,7 @@ export class Webhook extends Node {
|
|||
throw error;
|
||||
}
|
||||
|
||||
const prepareOutput = setupOutputConnection(context, {
|
||||
const prepareOutput = setupOutputConnection(context, requestMethod, {
|
||||
jwtPayload: validationData,
|
||||
});
|
||||
|
||||
|
|
|
@ -50,22 +50,34 @@ export const getResponseData = (parameters: WebhookParameters) => {
|
|||
};
|
||||
|
||||
export const configuredOutputs = (parameters: WebhookParameters) => {
|
||||
const httpMethod = parameters.httpMethod;
|
||||
const httpMethod = parameters.httpMethod as string | string[];
|
||||
|
||||
return [
|
||||
{
|
||||
if (!Array.isArray(httpMethod))
|
||||
return [
|
||||
{
|
||||
type: `${NodeConnectionType.Main}`,
|
||||
displayName: httpMethod,
|
||||
},
|
||||
];
|
||||
|
||||
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 +85,29 @@ export const setupOutputConnection = (
|
|||
webhookUrl = webhookUrl.replace('/webhook/', '/webhook-test/');
|
||||
}
|
||||
|
||||
// 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;
|
||||
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;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -996,7 +996,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,
|
||||
|
@ -1005,7 +1005,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.`,
|
||||
|
@ -1018,15 +1018,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;
|
||||
|
|
Loading…
Reference in a new issue