2024-03-28 01:46:39 -07:00
|
|
|
import { NodeConnectionType, NodeOperationError } from 'n8n-workflow';
|
|
|
|
import type { IWebhookFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow';
|
|
|
|
|
|
|
|
type WebhookParameters = {
|
|
|
|
httpMethod: string;
|
|
|
|
responseMode: string;
|
|
|
|
responseData: string;
|
|
|
|
responseCode?: number; //typeVersion <= 1.1
|
|
|
|
options?: {
|
|
|
|
responseData?: string;
|
|
|
|
responseCode?: {
|
|
|
|
values?: {
|
|
|
|
responseCode: number;
|
|
|
|
customCode?: number;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
noResponseBody?: boolean;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
export const getResponseCode = (parameters: WebhookParameters) => {
|
|
|
|
if (parameters.responseCode) {
|
|
|
|
return parameters.responseCode;
|
|
|
|
}
|
|
|
|
const responseCodeOptions = parameters.options;
|
|
|
|
if (responseCodeOptions?.responseCode?.values) {
|
|
|
|
const { responseCode, customCode } = responseCodeOptions.responseCode.values;
|
|
|
|
|
|
|
|
if (customCode) {
|
|
|
|
return customCode;
|
|
|
|
}
|
|
|
|
|
|
|
|
return responseCode;
|
|
|
|
}
|
|
|
|
return 200;
|
|
|
|
};
|
|
|
|
|
|
|
|
export const getResponseData = (parameters: WebhookParameters) => {
|
|
|
|
const { responseData, responseMode, options } = parameters;
|
|
|
|
if (responseData) return responseData;
|
|
|
|
|
|
|
|
if (responseMode === 'onReceived') {
|
|
|
|
const data = options?.responseData;
|
|
|
|
if (data) return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (options?.noResponseBody) return 'noData';
|
|
|
|
|
|
|
|
return undefined;
|
|
|
|
};
|
|
|
|
|
|
|
|
export const configuredOutputs = (parameters: WebhookParameters) => {
|
2024-04-23 22:46:16 -07:00
|
|
|
const httpMethod = parameters.httpMethod as string | string[];
|
|
|
|
|
|
|
|
if (!Array.isArray(httpMethod))
|
|
|
|
return [
|
|
|
|
{
|
|
|
|
type: `${NodeConnectionType.Main}`,
|
|
|
|
displayName: httpMethod,
|
|
|
|
},
|
|
|
|
];
|
|
|
|
|
|
|
|
const outputs = httpMethod.map((method) => {
|
|
|
|
return {
|
2024-03-28 01:46:39 -07:00
|
|
|
type: `${NodeConnectionType.Main}`,
|
2024-04-23 22:46:16 -07:00
|
|
|
displayName: method,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
return outputs;
|
2024-03-28 01:46:39 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
export const setupOutputConnection = (
|
|
|
|
ctx: IWebhookFunctions,
|
2024-04-23 22:46:16 -07:00
|
|
|
method: string,
|
2024-03-28 01:46:39 -07:00
|
|
|
additionalData: {
|
|
|
|
jwtPayload?: IDataObject;
|
|
|
|
},
|
|
|
|
) => {
|
2024-04-23 22:46:16 -07:00
|
|
|
const httpMethod = ctx.getNodeParameter('httpMethod', []) as string[] | string;
|
2024-03-28 01:46:39 -07:00
|
|
|
let webhookUrl = ctx.getNodeWebhookUrl('default') as string;
|
|
|
|
const executionMode = ctx.getMode() === 'manual' ? 'test' : 'production';
|
|
|
|
|
|
|
|
if (executionMode === 'test') {
|
|
|
|
webhookUrl = webhookUrl.replace('/webhook/', '/webhook-test/');
|
|
|
|
}
|
|
|
|
|
2024-04-23 22:46:16 -07:00
|
|
|
// 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(() => []);
|
|
|
|
|
2024-03-28 01:46:39 -07:00
|
|
|
return (outputData: INodeExecutionData): INodeExecutionData[][] => {
|
|
|
|
outputData.json.webhookUrl = webhookUrl;
|
|
|
|
outputData.json.executionMode = executionMode;
|
|
|
|
if (additionalData?.jwtPayload) {
|
|
|
|
outputData.json.jwtPayload = additionalData.jwtPayload;
|
|
|
|
}
|
2024-04-23 22:46:16 -07:00
|
|
|
outputs[outputIndex] = [outputData];
|
|
|
|
return outputs;
|
2024-03-28 01:46:39 -07:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
export const isIpWhitelisted = (
|
|
|
|
whitelist: string | string[] | undefined,
|
|
|
|
ips: string[],
|
|
|
|
ip?: string,
|
|
|
|
) => {
|
|
|
|
if (whitelist === undefined || whitelist === '') {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Array.isArray(whitelist)) {
|
|
|
|
whitelist = whitelist.split(',').map((entry) => entry.trim());
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const address of whitelist) {
|
|
|
|
if (ip && ip.includes(address)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ips.some((entry) => entry.includes(address))) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
|
|
|
export const checkResponseModeConfiguration = (context: IWebhookFunctions) => {
|
|
|
|
const responseMode = context.getNodeParameter('responseMode', 'onReceived') as string;
|
|
|
|
const connectedNodes = context.getChildNodes(context.getNode().name);
|
|
|
|
|
|
|
|
const isRespondToWebhookConnected = connectedNodes.some(
|
|
|
|
(node) => node.type === 'n8n-nodes-base.respondToWebhook',
|
|
|
|
);
|
|
|
|
|
|
|
|
if (!isRespondToWebhookConnected && responseMode === 'responseNode') {
|
|
|
|
throw new NodeOperationError(
|
|
|
|
context.getNode(),
|
|
|
|
new Error('No Respond to Webhook node found in the workflow'),
|
|
|
|
{
|
|
|
|
description:
|
|
|
|
'Insert a Respond to Webhook node to your workflow to respond to the webhook or choose another option for the “Respond” parameter',
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isRespondToWebhookConnected && responseMode !== 'responseNode') {
|
|
|
|
throw new NodeOperationError(
|
|
|
|
context.getNode(),
|
|
|
|
new Error('Webhook node not correctly configured'),
|
|
|
|
{
|
|
|
|
description:
|
|
|
|
'Set the “Respond” parameter to “Using Respond to Webhook Node” or remove the Respond to Webhook node',
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|