mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
refactor(core): Extract hook context out of NodeExecutionFunctions (no-changelog) (#11537)
This commit is contained in:
parent
0817f8571a
commit
19a5c2fcf1
|
@ -108,6 +108,7 @@ import type {
|
||||||
ICheckProcessedContextData,
|
ICheckProcessedContextData,
|
||||||
AiEvent,
|
AiEvent,
|
||||||
ISupplyDataFunctions,
|
ISupplyDataFunctions,
|
||||||
|
WebhookType,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import {
|
import {
|
||||||
NodeConnectionType,
|
NodeConnectionType,
|
||||||
|
@ -165,7 +166,7 @@ import { extractValue } from './ExtractValue';
|
||||||
import { InstanceSettings } from './InstanceSettings';
|
import { InstanceSettings } from './InstanceSettings';
|
||||||
import type { ExtendedValidationResult, IResponseError } from './Interfaces';
|
import type { ExtendedValidationResult, IResponseError } from './Interfaces';
|
||||||
// eslint-disable-next-line import/no-cycle
|
// eslint-disable-next-line import/no-cycle
|
||||||
import { PollContext, TriggerContext, WebhookContext } from './node-execution-context';
|
import { HookContext, PollContext, TriggerContext, WebhookContext } from './node-execution-context';
|
||||||
import { getSecretsProxy } from './Secrets';
|
import { getSecretsProxy } from './Secrets';
|
||||||
import { SSHClientsManager } from './SSHClientsManager';
|
import { SSHClientsManager } from './SSHClientsManager';
|
||||||
|
|
||||||
|
@ -2628,7 +2629,7 @@ export function continueOnFail(node: INode): boolean {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export function getNodeWebhookUrl(
|
export function getNodeWebhookUrl(
|
||||||
name: string,
|
name: WebhookType,
|
||||||
workflow: Workflow,
|
workflow: Workflow,
|
||||||
node: INode,
|
node: INode,
|
||||||
additionalData: IWorkflowExecuteAdditionalData,
|
additionalData: IWorkflowExecuteAdditionalData,
|
||||||
|
@ -2673,7 +2674,7 @@ export function getNodeWebhookUrl(
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export function getWebhookDescription(
|
export function getWebhookDescription(
|
||||||
name: string,
|
name: WebhookType,
|
||||||
workflow: Workflow,
|
workflow: Workflow,
|
||||||
node: INode,
|
node: INode,
|
||||||
): IWebhookDescription | undefined {
|
): IWebhookDescription | undefined {
|
||||||
|
@ -4342,59 +4343,7 @@ export function getExecuteHookFunctions(
|
||||||
activation: WorkflowActivateMode,
|
activation: WorkflowActivateMode,
|
||||||
webhookData?: IWebhookData,
|
webhookData?: IWebhookData,
|
||||||
): IHookFunctions {
|
): IHookFunctions {
|
||||||
return ((workflow: Workflow, node: INode) => {
|
return new HookContext(workflow, node, additionalData, mode, activation, webhookData);
|
||||||
return {
|
|
||||||
...getCommonWorkflowFunctions(workflow, node, additionalData),
|
|
||||||
getCredentials: async (type) =>
|
|
||||||
await getCredentials(workflow, node, type, additionalData, mode),
|
|
||||||
getMode: () => mode,
|
|
||||||
getActivationMode: () => activation,
|
|
||||||
getNodeParameter: (
|
|
||||||
parameterName: string,
|
|
||||||
fallbackValue?: any,
|
|
||||||
options?: IGetNodeParameterOptions,
|
|
||||||
): NodeParameterValueType | object => {
|
|
||||||
const runExecutionData: IRunExecutionData | null = null;
|
|
||||||
const itemIndex = 0;
|
|
||||||
const runIndex = 0;
|
|
||||||
const connectionInputData: INodeExecutionData[] = [];
|
|
||||||
|
|
||||||
return getNodeParameter(
|
|
||||||
workflow,
|
|
||||||
runExecutionData,
|
|
||||||
runIndex,
|
|
||||||
connectionInputData,
|
|
||||||
node,
|
|
||||||
parameterName,
|
|
||||||
itemIndex,
|
|
||||||
mode,
|
|
||||||
getAdditionalKeys(additionalData, mode, runExecutionData),
|
|
||||||
undefined,
|
|
||||||
fallbackValue,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
getNodeWebhookUrl: (name: string): string | undefined => {
|
|
||||||
return getNodeWebhookUrl(
|
|
||||||
name,
|
|
||||||
workflow,
|
|
||||||
node,
|
|
||||||
additionalData,
|
|
||||||
mode,
|
|
||||||
getAdditionalKeys(additionalData, mode, null),
|
|
||||||
webhookData?.isTest,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
getWebhookName(): string {
|
|
||||||
if (webhookData === undefined) {
|
|
||||||
throw new ApplicationError('Only supported in webhook functions');
|
|
||||||
}
|
|
||||||
return webhookData.webhookDescription.name;
|
|
||||||
},
|
|
||||||
getWebhookDescription: (name) => getWebhookDescription(name, workflow, node),
|
|
||||||
helpers: getRequestHelperFunctions(workflow, node, additionalData),
|
|
||||||
};
|
|
||||||
})(workflow, node);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,147 @@
|
||||||
|
import { mock } from 'jest-mock-extended';
|
||||||
|
import type {
|
||||||
|
Expression,
|
||||||
|
ICredentialDataDecryptedObject,
|
||||||
|
ICredentialsHelper,
|
||||||
|
INode,
|
||||||
|
INodeType,
|
||||||
|
INodeTypes,
|
||||||
|
IWebhookDescription,
|
||||||
|
IWebhookData,
|
||||||
|
IWorkflowExecuteAdditionalData,
|
||||||
|
Workflow,
|
||||||
|
WorkflowActivateMode,
|
||||||
|
WorkflowExecuteMode,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import { ApplicationError } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { HookContext } from '../hook-context';
|
||||||
|
|
||||||
|
describe('HookContext', () => {
|
||||||
|
const testCredentialType = 'testCredential';
|
||||||
|
const webhookDescription: IWebhookDescription = {
|
||||||
|
name: 'default',
|
||||||
|
httpMethod: 'GET',
|
||||||
|
responseMode: 'onReceived',
|
||||||
|
path: 'testPath',
|
||||||
|
};
|
||||||
|
const nodeType = mock<INodeType>({
|
||||||
|
description: {
|
||||||
|
credentials: [
|
||||||
|
{
|
||||||
|
name: testCredentialType,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
name: 'testParameter',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
nodeType.description.webhooks = [webhookDescription];
|
||||||
|
const nodeTypes = mock<INodeTypes>();
|
||||||
|
const expression = mock<Expression>();
|
||||||
|
const workflow = mock<Workflow>({ expression, nodeTypes });
|
||||||
|
const node = mock<INode>({
|
||||||
|
credentials: {
|
||||||
|
[testCredentialType]: {
|
||||||
|
id: 'testCredentialId',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
node.parameters = {
|
||||||
|
testParameter: 'testValue',
|
||||||
|
};
|
||||||
|
const credentialsHelper = mock<ICredentialsHelper>();
|
||||||
|
const additionalData = mock<IWorkflowExecuteAdditionalData>({ credentialsHelper });
|
||||||
|
const mode: WorkflowExecuteMode = 'manual';
|
||||||
|
const activation: WorkflowActivateMode = 'init';
|
||||||
|
const webhookData = mock<IWebhookData>({
|
||||||
|
webhookDescription: {
|
||||||
|
name: 'default',
|
||||||
|
isFullPath: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const hookContext = new HookContext(
|
||||||
|
workflow,
|
||||||
|
node,
|
||||||
|
additionalData,
|
||||||
|
mode,
|
||||||
|
activation,
|
||||||
|
webhookData,
|
||||||
|
);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
nodeTypes.getByNameAndVersion.mockReturnValue(nodeType);
|
||||||
|
expression.getParameterValue.mockImplementation((value) => value);
|
||||||
|
expression.getSimpleParameterValue.mockImplementation((_, value) => value);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getActivationMode', () => {
|
||||||
|
it('should return the activation property', () => {
|
||||||
|
const result = hookContext.getActivationMode();
|
||||||
|
expect(result).toBe(activation);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getCredentials', () => {
|
||||||
|
it('should get decrypted credentials', async () => {
|
||||||
|
nodeTypes.getByNameAndVersion.mockReturnValue(nodeType);
|
||||||
|
credentialsHelper.getDecrypted.mockResolvedValue({ secret: 'token' });
|
||||||
|
|
||||||
|
const credentials =
|
||||||
|
await hookContext.getCredentials<ICredentialDataDecryptedObject>(testCredentialType);
|
||||||
|
|
||||||
|
expect(credentials).toEqual({ secret: 'token' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getNodeParameter', () => {
|
||||||
|
it('should return parameter value when it exists', () => {
|
||||||
|
const parameter = hookContext.getNodeParameter('testParameter');
|
||||||
|
|
||||||
|
expect(parameter).toBe('testValue');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getNodeWebhookUrl', () => {
|
||||||
|
it('should return node webhook url', () => {
|
||||||
|
const url = hookContext.getNodeWebhookUrl('default');
|
||||||
|
|
||||||
|
expect(url).toContain('testPath');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getWebhookName', () => {
|
||||||
|
it('should return webhook name', () => {
|
||||||
|
const name = hookContext.getWebhookName();
|
||||||
|
|
||||||
|
expect(name).toBe('default');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error if webhookData is undefined', () => {
|
||||||
|
const hookContextWithoutWebhookData = new HookContext(
|
||||||
|
workflow,
|
||||||
|
node,
|
||||||
|
additionalData,
|
||||||
|
mode,
|
||||||
|
activation,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(() => hookContextWithoutWebhookData.getWebhookName()).toThrow(ApplicationError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getWebhookDescription', () => {
|
||||||
|
it('should return webhook description', () => {
|
||||||
|
const description = hookContext.getWebhookDescription('default');
|
||||||
|
|
||||||
|
expect(description).toEqual<IWebhookDescription>(webhookDescription);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
103
packages/core/src/node-execution-context/hook-context.ts
Normal file
103
packages/core/src/node-execution-context/hook-context.ts
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
import type {
|
||||||
|
ICredentialDataDecryptedObject,
|
||||||
|
IGetNodeParameterOptions,
|
||||||
|
INode,
|
||||||
|
INodeExecutionData,
|
||||||
|
IHookFunctions,
|
||||||
|
IRunExecutionData,
|
||||||
|
IWorkflowExecuteAdditionalData,
|
||||||
|
NodeParameterValueType,
|
||||||
|
Workflow,
|
||||||
|
WorkflowActivateMode,
|
||||||
|
WorkflowExecuteMode,
|
||||||
|
IWebhookData,
|
||||||
|
WebhookType,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import { ApplicationError } from 'n8n-workflow';
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-cycle
|
||||||
|
import {
|
||||||
|
getAdditionalKeys,
|
||||||
|
getCredentials,
|
||||||
|
getNodeParameter,
|
||||||
|
getNodeWebhookUrl,
|
||||||
|
getWebhookDescription,
|
||||||
|
} from '@/NodeExecuteFunctions';
|
||||||
|
|
||||||
|
import { RequestHelpers } from './helpers/request-helpers';
|
||||||
|
import { NodeExecutionContext } from './node-execution-context';
|
||||||
|
|
||||||
|
export class HookContext extends NodeExecutionContext implements IHookFunctions {
|
||||||
|
readonly helpers: IHookFunctions['helpers'];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
workflow: Workflow,
|
||||||
|
node: INode,
|
||||||
|
additionalData: IWorkflowExecuteAdditionalData,
|
||||||
|
mode: WorkflowExecuteMode,
|
||||||
|
private readonly activation: WorkflowActivateMode,
|
||||||
|
private readonly webhookData?: IWebhookData,
|
||||||
|
) {
|
||||||
|
super(workflow, node, additionalData, mode);
|
||||||
|
|
||||||
|
this.helpers = new RequestHelpers(this, workflow, node, additionalData);
|
||||||
|
}
|
||||||
|
|
||||||
|
getActivationMode() {
|
||||||
|
return this.activation;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCredentials<T extends object = ICredentialDataDecryptedObject>(type: string) {
|
||||||
|
return await getCredentials<T>(this.workflow, this.node, type, this.additionalData, this.mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
getNodeParameter(
|
||||||
|
parameterName: string,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
fallbackValue?: any,
|
||||||
|
options?: IGetNodeParameterOptions,
|
||||||
|
): NodeParameterValueType | object {
|
||||||
|
const runExecutionData: IRunExecutionData | null = null;
|
||||||
|
const itemIndex = 0;
|
||||||
|
const runIndex = 0;
|
||||||
|
const connectionInputData: INodeExecutionData[] = [];
|
||||||
|
|
||||||
|
return getNodeParameter(
|
||||||
|
this.workflow,
|
||||||
|
runExecutionData,
|
||||||
|
runIndex,
|
||||||
|
connectionInputData,
|
||||||
|
this.node,
|
||||||
|
parameterName,
|
||||||
|
itemIndex,
|
||||||
|
this.mode,
|
||||||
|
getAdditionalKeys(this.additionalData, this.mode, runExecutionData),
|
||||||
|
undefined,
|
||||||
|
fallbackValue,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getNodeWebhookUrl(name: WebhookType): string | undefined {
|
||||||
|
return getNodeWebhookUrl(
|
||||||
|
name,
|
||||||
|
this.workflow,
|
||||||
|
this.node,
|
||||||
|
this.additionalData,
|
||||||
|
this.mode,
|
||||||
|
getAdditionalKeys(this.additionalData, this.mode, null),
|
||||||
|
this.webhookData?.isTest,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getWebhookName(): string {
|
||||||
|
if (this.webhookData === undefined) {
|
||||||
|
throw new ApplicationError('Only supported in webhook functions');
|
||||||
|
}
|
||||||
|
return this.webhookData.webhookDescription.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
getWebhookDescription(name: WebhookType) {
|
||||||
|
return getWebhookDescription(name, this.workflow, this.node);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
// eslint-disable-next-line import/no-cycle
|
// eslint-disable-next-line import/no-cycle
|
||||||
|
export { HookContext } from './hook-context';
|
||||||
export { LoadOptionsContext } from './load-options-context';
|
export { LoadOptionsContext } from './load-options-context';
|
||||||
export { PollContext } from './poll-context';
|
export { PollContext } from './poll-context';
|
||||||
export { TriggerContext } from './trigger-context';
|
export { TriggerContext } from './trigger-context';
|
||||||
|
|
|
@ -14,6 +14,7 @@ import type {
|
||||||
IWorkflowExecuteAdditionalData,
|
IWorkflowExecuteAdditionalData,
|
||||||
NodeConnectionType,
|
NodeConnectionType,
|
||||||
NodeParameterValueType,
|
NodeParameterValueType,
|
||||||
|
WebhookType,
|
||||||
Workflow,
|
Workflow,
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
@ -108,7 +109,7 @@ export class WebhookContext extends NodeExecutionContext implements IWebhookFunc
|
||||||
return httpRequest;
|
return httpRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
getNodeWebhookUrl(name: string): string | undefined {
|
getNodeWebhookUrl(name: WebhookType): string | undefined {
|
||||||
return getNodeWebhookUrl(
|
return getNodeWebhookUrl(
|
||||||
name,
|
name,
|
||||||
this.workflow,
|
this.workflow,
|
||||||
|
|
|
@ -1104,8 +1104,8 @@ export interface ITriggerFunctions
|
||||||
export interface IHookFunctions
|
export interface IHookFunctions
|
||||||
extends FunctionsBaseWithRequiredKeys<'getMode' | 'getActivationMode'> {
|
extends FunctionsBaseWithRequiredKeys<'getMode' | 'getActivationMode'> {
|
||||||
getWebhookName(): string;
|
getWebhookName(): string;
|
||||||
getWebhookDescription(name: string): IWebhookDescription | undefined;
|
getWebhookDescription(name: WebhookType): IWebhookDescription | undefined;
|
||||||
getNodeWebhookUrl: (name: string) => string | undefined;
|
getNodeWebhookUrl: (name: WebhookType) => string | undefined;
|
||||||
getNodeParameter(
|
getNodeParameter(
|
||||||
parameterName: string,
|
parameterName: string,
|
||||||
fallbackValue?: any,
|
fallbackValue?: any,
|
||||||
|
@ -1127,7 +1127,7 @@ export interface IWebhookFunctions extends FunctionsBaseWithRequiredKeys<'getMod
|
||||||
fallbackValue?: any,
|
fallbackValue?: any,
|
||||||
options?: IGetNodeParameterOptions,
|
options?: IGetNodeParameterOptions,
|
||||||
): NodeParameterValueType | object;
|
): NodeParameterValueType | object;
|
||||||
getNodeWebhookUrl: (name: string) => string | undefined;
|
getNodeWebhookUrl: (name: WebhookType) => string | undefined;
|
||||||
evaluateExpression(expression: string, itemIndex?: number): NodeParameterValueType;
|
evaluateExpression(expression: string, itemIndex?: number): NodeParameterValueType;
|
||||||
getParamsData(): object;
|
getParamsData(): object;
|
||||||
getQueryData(): object;
|
getQueryData(): object;
|
||||||
|
@ -1619,7 +1619,7 @@ export interface INodeType {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
webhookMethods?: {
|
webhookMethods?: {
|
||||||
[name in IWebhookDescription['name']]?: {
|
[name in WebhookType]?: {
|
||||||
[method in WebhookSetupMethodNames]: (this: IHookFunctions) => Promise<boolean>;
|
[method in WebhookSetupMethodNames]: (this: IHookFunctions) => Promise<boolean>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1972,11 +1972,13 @@ export interface IWebhookData {
|
||||||
staticData?: Workflow['staticData'];
|
staticData?: Workflow['staticData'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type WebhookType = 'default' | 'setup';
|
||||||
|
|
||||||
export interface IWebhookDescription {
|
export interface IWebhookDescription {
|
||||||
[key: string]: IHttpRequestMethods | WebhookResponseMode | boolean | string | undefined;
|
[key: string]: IHttpRequestMethods | WebhookResponseMode | boolean | string | undefined;
|
||||||
httpMethod: IHttpRequestMethods | string;
|
httpMethod: IHttpRequestMethods | string;
|
||||||
isFullPath?: boolean;
|
isFullPath?: boolean;
|
||||||
name: 'default' | 'setup';
|
name: WebhookType;
|
||||||
path: string;
|
path: string;
|
||||||
responseBinaryPropertyName?: string;
|
responseBinaryPropertyName?: string;
|
||||||
responseContentType?: string;
|
responseContentType?: string;
|
||||||
|
|
Loading…
Reference in a new issue