mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
refactor(core): Improve test-webhooks (no-changelog) (#8069)
Remove duplication, improve readability, and expand tests for `TestWebhooks.ts` - in anticipation for storing test webhooks in Redis. --------- Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
@@ -1,205 +0,0 @@
|
||||
import { Service } from 'typedi';
|
||||
import type {
|
||||
IWebhookData,
|
||||
IHttpRequestMethods,
|
||||
Workflow,
|
||||
WorkflowActivateMode,
|
||||
WorkflowExecuteMode,
|
||||
} from 'n8n-workflow';
|
||||
import { ApplicationError, WebhookPathTakenError } from 'n8n-workflow';
|
||||
import * as NodeExecuteFunctions from 'n8n-core';
|
||||
|
||||
@Service()
|
||||
export class ActiveWebhooks {
|
||||
private workflowWebhooks: {
|
||||
[key: string]: IWebhookData[];
|
||||
} = {};
|
||||
|
||||
private webhookUrls: {
|
||||
[key: string]: IWebhookData[];
|
||||
} = {};
|
||||
|
||||
testWebhooks = false;
|
||||
|
||||
/**
|
||||
* Adds a new webhook
|
||||
*
|
||||
*/
|
||||
async add(
|
||||
workflow: Workflow,
|
||||
webhookData: IWebhookData,
|
||||
mode: WorkflowExecuteMode,
|
||||
activation: WorkflowActivateMode,
|
||||
): Promise<void> {
|
||||
if (workflow.id === undefined) {
|
||||
throw new ApplicationError(
|
||||
'Webhooks can only be added for saved workflows as an ID is needed',
|
||||
);
|
||||
}
|
||||
if (webhookData.path.endsWith('/')) {
|
||||
webhookData.path = webhookData.path.slice(0, -1);
|
||||
}
|
||||
|
||||
const webhookKey = this.getWebhookKey(
|
||||
webhookData.httpMethod,
|
||||
webhookData.path,
|
||||
webhookData.webhookId,
|
||||
);
|
||||
|
||||
// check that there is not a webhook already registered with that path/method
|
||||
if (this.webhookUrls[webhookKey] && !webhookData.webhookId) {
|
||||
throw new WebhookPathTakenError(webhookData.node);
|
||||
}
|
||||
|
||||
if (this.workflowWebhooks[webhookData.workflowId] === undefined) {
|
||||
this.workflowWebhooks[webhookData.workflowId] = [];
|
||||
}
|
||||
|
||||
// Make the webhook available directly because sometimes to create it successfully
|
||||
// it gets called
|
||||
if (!this.webhookUrls[webhookKey]) {
|
||||
this.webhookUrls[webhookKey] = [];
|
||||
}
|
||||
this.webhookUrls[webhookKey].push(webhookData);
|
||||
|
||||
try {
|
||||
await workflow.createWebhookIfNotExists(
|
||||
webhookData,
|
||||
NodeExecuteFunctions,
|
||||
mode,
|
||||
activation,
|
||||
this.testWebhooks,
|
||||
);
|
||||
} catch (error) {
|
||||
// If there was a problem unregister the webhook again
|
||||
if (this.webhookUrls[webhookKey].length <= 1) {
|
||||
delete this.webhookUrls[webhookKey];
|
||||
} else {
|
||||
this.webhookUrls[webhookKey] = this.webhookUrls[webhookKey].filter(
|
||||
(webhook) => webhook.path !== webhookData.path,
|
||||
);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
this.workflowWebhooks[webhookData.workflowId].push(webhookData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns webhookData if a webhook with matches is currently registered
|
||||
*
|
||||
* @param {(string | undefined)} webhookId
|
||||
*/
|
||||
get(httpMethod: IHttpRequestMethods, path: string, webhookId?: string): IWebhookData | undefined {
|
||||
const webhookKey = this.getWebhookKey(httpMethod, path, webhookId);
|
||||
if (this.webhookUrls[webhookKey] === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let webhook: IWebhookData | undefined;
|
||||
let maxMatches = 0;
|
||||
const pathElementsSet = new Set(path.split('/'));
|
||||
// check if static elements match in path
|
||||
// if more results have been returned choose the one with the most static-route matches
|
||||
this.webhookUrls[webhookKey].forEach((dynamicWebhook) => {
|
||||
const staticElements = dynamicWebhook.path.split('/').filter((ele) => !ele.startsWith(':'));
|
||||
const allStaticExist = staticElements.every((staticEle) => pathElementsSet.has(staticEle));
|
||||
|
||||
if (allStaticExist && staticElements.length > maxMatches) {
|
||||
maxMatches = staticElements.length;
|
||||
webhook = dynamicWebhook;
|
||||
}
|
||||
// handle routes with no static elements
|
||||
else if (staticElements.length === 0 && !webhook) {
|
||||
webhook = dynamicWebhook;
|
||||
}
|
||||
});
|
||||
|
||||
return webhook;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all request methods associated with a single webhook
|
||||
*/
|
||||
getWebhookMethods(path: string): IHttpRequestMethods[] {
|
||||
return Object.keys(this.webhookUrls)
|
||||
.filter((key) => key.includes(path))
|
||||
.map((key) => key.split('|')[0] as IHttpRequestMethods);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ids of all the workflows which have active webhooks
|
||||
*
|
||||
*/
|
||||
getWorkflowIds(): string[] {
|
||||
return Object.keys(this.workflowWebhooks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns key to uniquely identify a webhook
|
||||
*
|
||||
* @param {(string | undefined)} webhookId
|
||||
*/
|
||||
getWebhookKey(httpMethod: IHttpRequestMethods, path: string, webhookId?: string): string {
|
||||
if (webhookId) {
|
||||
if (path.startsWith(webhookId)) {
|
||||
const cutFromIndex = path.indexOf('/') + 1;
|
||||
|
||||
path = path.slice(cutFromIndex);
|
||||
}
|
||||
return `${httpMethod}|${webhookId}|${path.split('/').length}`;
|
||||
}
|
||||
return `${httpMethod}|${path}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all webhooks of a workflow
|
||||
*
|
||||
*/
|
||||
async removeWorkflow(workflow: Workflow): Promise<boolean> {
|
||||
const workflowId = workflow.id;
|
||||
|
||||
if (this.workflowWebhooks[workflowId] === undefined) {
|
||||
// If it did not exist then there is nothing to remove
|
||||
return false;
|
||||
}
|
||||
|
||||
const webhooks = this.workflowWebhooks[workflowId];
|
||||
|
||||
const mode = 'internal';
|
||||
|
||||
// Go through all the registered webhooks of the workflow and remove them
|
||||
|
||||
for (const webhookData of webhooks) {
|
||||
await workflow.deleteWebhook(
|
||||
webhookData,
|
||||
NodeExecuteFunctions,
|
||||
mode,
|
||||
'update',
|
||||
this.testWebhooks,
|
||||
);
|
||||
|
||||
delete this.webhookUrls[
|
||||
this.getWebhookKey(webhookData.httpMethod, webhookData.path, webhookData.webhookId)
|
||||
];
|
||||
}
|
||||
|
||||
// Remove also the workflow-webhook entry
|
||||
delete this.workflowWebhooks[workflowId];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all the webhooks of the given workflows
|
||||
*/
|
||||
async removeAll(workflows: Workflow[]): Promise<void> {
|
||||
const removePromises = [];
|
||||
|
||||
for (const workflow of workflows) {
|
||||
removePromises.push(this.removeWorkflow(workflow));
|
||||
}
|
||||
|
||||
await Promise.all(removePromises);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user