mirror of
https://github.com/n8n-io/n8n.git
synced 2026-06-02 17:57:06 +02:00
149 lines
4.6 KiB
TypeScript
149 lines
4.6 KiB
TypeScript
import { Logger } from '@n8n/backend-common';
|
|
import { Service } from '@n8n/di';
|
|
import {
|
|
IExecuteData,
|
|
IExecutionContext,
|
|
INodeExecutionData,
|
|
PlaintextExecutionContext,
|
|
toCredentialContext,
|
|
toExecutionContextEstablishmentHookParameter,
|
|
Workflow,
|
|
} from 'n8n-workflow';
|
|
|
|
import { Cipher } from '@/encryption';
|
|
import { deepMerge } from '@/utils/deep-merge';
|
|
|
|
import { ExecutionContextHookRegistry } from './execution-context-hook-registry.service';
|
|
|
|
@Service()
|
|
export class ExecutionContextService {
|
|
constructor(
|
|
private readonly logger: Logger,
|
|
private readonly executionContextHookRegistry: ExecutionContextHookRegistry,
|
|
private readonly cipher: Cipher,
|
|
) {}
|
|
|
|
decryptExecutionContext(context: IExecutionContext): PlaintextExecutionContext {
|
|
let credentials = undefined;
|
|
if (context.credentials) {
|
|
const decrypted = this.cipher.decrypt(context.credentials);
|
|
credentials = toCredentialContext(decrypted);
|
|
}
|
|
return {
|
|
...context,
|
|
credentials,
|
|
};
|
|
}
|
|
|
|
encryptExecutionContext(context: PlaintextExecutionContext): IExecutionContext {
|
|
let credentials = undefined;
|
|
if (context.credentials) {
|
|
credentials = this.cipher.encrypt(context.credentials);
|
|
}
|
|
return {
|
|
...context,
|
|
credentials,
|
|
};
|
|
}
|
|
|
|
mergeExecutionContexts(
|
|
baseContext: PlaintextExecutionContext,
|
|
contextToMerge: Partial<PlaintextExecutionContext>,
|
|
): PlaintextExecutionContext {
|
|
return deepMerge(baseContext, contextToMerge);
|
|
}
|
|
|
|
// startItem is mutated to reflect any changes to trigger items made by the hooks
|
|
async augmentExecutionContextWithHooks(
|
|
workflow: Workflow,
|
|
startItem: IExecuteData,
|
|
contextToAugment: IExecutionContext,
|
|
): Promise<{
|
|
context: IExecutionContext;
|
|
triggerItems: INodeExecutionData[] | null;
|
|
}> {
|
|
// Main input data is an array of items, each item represents an event that triggers the workflow execution
|
|
// The 'main' selector selects the input name of the nodes, and the 0 index represents the runIndex,
|
|
// 0 being the first run of this node in the workflow.
|
|
|
|
let currentTriggerItems = startItem.data['main'][0];
|
|
|
|
const contextEstablishmentHookParameters = {
|
|
...(workflow.getNode(startItem.node.name)?.parameters ?? {}),
|
|
...startItem.node.parameters,
|
|
};
|
|
|
|
const startNodeParametersResult = toExecutionContextEstablishmentHookParameter(
|
|
contextEstablishmentHookParameters,
|
|
);
|
|
|
|
if (!startNodeParametersResult || startNodeParametersResult.error) {
|
|
if (startNodeParametersResult?.error) {
|
|
this.logger.warn(
|
|
`Failed to parse execution context establishment hook parameters for node ${startItem.node.name}: ${startNodeParametersResult.error.message}`,
|
|
);
|
|
}
|
|
// no execution establishment hooks found, we just return the original context
|
|
return {
|
|
context: contextToAugment,
|
|
triggerItems: currentTriggerItems,
|
|
};
|
|
}
|
|
|
|
// startNodeParameters will hold the parameters of the start node
|
|
// this can be the settings for the different hooks to be executed
|
|
// for example to extract the bearer token from the start node data.
|
|
const startNodeParameters = startNodeParametersResult.data;
|
|
|
|
// decrypt the context to work with plaintext data
|
|
let context = this.decryptExecutionContext(contextToAugment);
|
|
|
|
// based on startNodeParameters, startNodeType and currentTriggerItems we can now
|
|
// iterate over the different hooks to extract specific data for the runtime context
|
|
for (const hookParameters of startNodeParameters.contextEstablishmentHooks.hooks) {
|
|
const hook = this.executionContextHookRegistry.getHookByName(hookParameters.hookName);
|
|
|
|
if (!hook) {
|
|
this.logger.warn(
|
|
`Execution context establishment hook ${hookParameters.hookName} not found, skipping this hook`,
|
|
);
|
|
continue;
|
|
}
|
|
try {
|
|
// call the hook to let it modify the context and/or the main input data
|
|
const result = await hook.execute({
|
|
triggerNode: startItem.node,
|
|
workflow,
|
|
triggerItems: currentTriggerItems,
|
|
context,
|
|
options: hookParameters,
|
|
});
|
|
|
|
if (result.triggerItems !== undefined) {
|
|
// Update trigger items in case they were modified by the hook
|
|
currentTriggerItems = result.triggerItems;
|
|
}
|
|
|
|
if (result.contextUpdate) {
|
|
// Merge any returned context fields into the execution context
|
|
context = this.mergeExecutionContexts(context, result.contextUpdate);
|
|
}
|
|
} catch (error) {
|
|
this.logger.warn(
|
|
`Failed to execute context establishment hook ${hookParameters.hookName}`,
|
|
{ error },
|
|
);
|
|
if (!hookParameters.isAllowedToFail) {
|
|
// If the hook is not allowed to fail, rethrow the error
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
context: this.encryptExecutionContext(context),
|
|
triggerItems: currentTriggerItems,
|
|
};
|
|
}
|
|
}
|