"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getLifecycleHooksForSubExecutions = getLifecycleHooksForSubExecutions;
exports.getLifecycleHooksForScalingWorker = getLifecycleHooksForScalingWorker;
exports.getLifecycleHooksForScalingMain = getLifecycleHooksForScalingMain;
exports.getLifecycleHooksForRegularMain = getLifecycleHooksForRegularMain;
const backend_common_1 = require("@n8n/backend-common");
const db_1 = require("@n8n/db");
const decorators_1 = require("@n8n/decorators");
const di_1 = require("@n8n/di");
const flatted_1 = require("flatted");
const n8n_core_1 = require("n8n-core");
const event_service_1 = require("../events/event.service");
const execution_persistence_1 = require("../executions/execution-persistence");
const execution_redaction_proxy_service_1 = require("../executions/execution-redaction-proxy.service");
const external_hooks_1 = require("../external-hooks");
const push_1 = require("../push");
const workflow_statistics_service_1 = require("../services/workflow-statistics.service");
const utils_1 = require("../utils");
const get_item_count_by_connection_type_1 = require("../utils/get-item-count-by-connection-type");
const workflow_helpers_1 = require("../workflow-helpers");
const workflow_static_data_service_1 = require("../workflows/workflow-static-data.service");
const execute_error_workflow_1 = require("./execute-error-workflow");
const restore_binary_data_id_1 = require("./restore-binary-data-id");
const save_execution_progress_1 = require("./save-execution-progress");
const shared_hook_functions_1 = require("./shared/shared-hook-functions");
const to_save_settings_1 = require("./to-save-settings");
let ModulesHooksRegistry = class ModulesHooksRegistry {
    addHooks(hooks) {
        const handlers = di_1.Container.get(decorators_1.LifecycleMetadata).getHandlers();
        for (const { handlerClass, methodName, eventName } of handlers) {
            const instance = di_1.Container.get(handlerClass);
            switch (eventName) {
                case 'workflowExecuteAfter':
                    hooks.addHandler(eventName, async function (runData, newStaticData) {
                        const context = {
                            type: 'workflowExecuteAfter',
                            workflow: this.workflowData,
                            runData,
                            newStaticData,
                            executionId: this.executionId,
                            retryOf: this.retryOf,
                        };
                        return await instance[methodName].call(instance, context);
                    });
                    break;
                case 'nodeExecuteBefore':
                    hooks.addHandler(eventName, async function (nodeName, taskData) {
                        const context = {
                            type: 'nodeExecuteBefore',
                            workflow: this.workflowData,
                            nodeName,
                            taskData,
                            executionId: this.executionId,
                        };
                        return await instance[methodName].call(instance, context);
                    });
                    break;
                case 'nodeExecuteAfter':
                    hooks.addHandler(eventName, async function (nodeName, taskData, executionData) {
                        const context = {
                            type: 'nodeExecuteAfter',
                            workflow: this.workflowData,
                            nodeName,
                            taskData,
                            executionData,
                            executionId: this.executionId,
                        };
                        return await instance[methodName].call(instance, context);
                    });
                    break;
                case 'workflowExecuteBefore':
                    hooks.addHandler(eventName, async function (workflowInstance, executionData) {
                        const context = {
                            type: 'workflowExecuteBefore',
                            workflow: this.workflowData,
                            workflowInstance,
                            executionData,
                            executionId: this.executionId,
                        };
                        return await instance[methodName].call(instance, context);
                    });
                    break;
                case 'workflowExecuteResume':
                    hooks.addHandler(eventName, async function (workflowInstance, executionData) {
                        const context = {
                            type: 'workflowExecuteResume',
                            workflow: this.workflowData,
                            workflowInstance,
                            executionData,
                            executionId: this.executionId,
                        };
                        return await instance[methodName].call(instance, context);
                    });
                    break;
            }
        }
    }
};
ModulesHooksRegistry = __decorate([
    (0, di_1.Service)()
], ModulesHooksRegistry);
function hookFunctionsWorkflowEvents(hooks, userId, projectId, projectName) {
    const eventService = di_1.Container.get(event_service_1.EventService);
    hooks.addHandler('workflowExecuteBefore', function () {
        const { executionId, workflowData, mode } = this;
        eventService.emit('workflow-pre-execute', {
            executionId,
            data: workflowData,
            mode,
            projectId,
            projectName,
        });
    });
    hooks.addHandler('workflowExecuteAfter', function (runData) {
        if (runData.status === 'waiting')
            return;
        const { executionId, workflowData: workflow } = this;
        if (runData.data.startData) {
            const originalDestination = runData.data.startData.originalDestinationNode;
            if (originalDestination) {
                runData.data.startData.destinationNode = originalDestination;
                runData.data.startData.originalDestinationNode = undefined;
            }
        }
        eventService.emit('workflow-post-execute', {
            executionId,
            runData,
            workflow,
            userId,
            projectId,
            projectName,
        });
    });
}
function hookFunctionsNodeEvents(hooks) {
    const eventService = di_1.Container.get(event_service_1.EventService);
    hooks.addHandler('nodeExecuteBefore', function (nodeName) {
        const { executionId, workflowData: workflow } = this;
        const node = workflow.nodes.find((n) => n.name === nodeName);
        eventService.emit('node-pre-execute', {
            executionId,
            workflow,
            nodeId: node?.id,
            nodeName,
            nodeType: node?.type,
        });
    });
    hooks.addHandler('nodeExecuteAfter', function (nodeName) {
        const { executionId, workflowData: workflow } = this;
        const node = workflow.nodes.find((n) => n.name === nodeName);
        eventService.emit('node-post-execute', {
            executionId,
            workflow,
            nodeId: node?.id,
            nodeName,
            nodeType: node?.type,
        });
    });
}
function buildRedactableExecution(hooks, runData, executionData) {
    return {
        id: hooks.executionId,
        mode: hooks.mode,
        workflowId: hooks.workflowData.id,
        data: {
            resultData: { runData },
            executionData: executionData?.executionData,
        },
        workflowData: {
            settings: hooks.workflowData.settings,
            nodes: hooks.workflowData.nodes,
        },
    };
}
function hookFunctionsPush(hooks, { pushRef, retryOf }, userId) {
    if (!pushRef)
        return;
    const logger = di_1.Container.get(backend_common_1.Logger);
    const pushInstance = di_1.Container.get(push_1.Push);
    const redactionProxy = di_1.Container.get(execution_redaction_proxy_service_1.ExecutionRedactionServiceProxy);
    const userRepository = di_1.Container.get(db_1.UserRepository);
    let resolvedUser;
    async function getUser() {
        if (resolvedUser !== undefined)
            return resolvedUser;
        resolvedUser = userId
            ? await userRepository.findOne({ where: { id: userId }, relations: ['role'] })
            : null;
        return resolvedUser;
    }
    hooks.addHandler('nodeExecuteBefore', function (nodeName, data) {
        const { executionId } = this;
        logger.debug(`Executing hook on node "${nodeName}" (hookFunctionsPush)`, {
            executionId,
            pushRef,
            workflowId: this.workflowData.id,
        });
        pushInstance.send({ type: 'nodeExecuteBefore', data: { executionId, nodeName, data } }, pushRef);
    });
    hooks.addHandler('nodeExecuteAfter', async function (nodeName, data, executionData) {
        const { executionId } = this;
        logger.debug(`Executing hook on node "${nodeName}" (hookFunctionsPush)`, {
            executionId,
            pushRef,
            workflowId: this.workflowData.id,
        });
        const itemCountByConnectionType = (0, get_item_count_by_connection_type_1.getItemCountByConnectionType)(data?.data);
        const { data: _, ...taskData } = data;
        pushInstance.send({
            type: 'nodeExecuteAfter',
            data: { executionId, nodeName, itemCountByConnectionType, data: taskData },
        }, pushRef);
        const user = await getUser();
        if (!user) {
            logger.warn('Skipping execution data push: unable to resolve user for redaction', {
                executionId,
                nodeName,
                userId,
            });
            return;
        }
        let dataToSend = data;
        try {
            const dummy = buildRedactableExecution(this, { [nodeName]: [data] }, executionData);
            const result = await redactionProxy.processExecution(dummy, {
                user,
                keepOriginal: true,
            });
            if (result !== dummy) {
                dataToSend = result.data.resultData.runData[nodeName][0];
            }
        }
        catch (error) {
            logger.error('Failed to redact push data, skipping nodeExecuteAfterData', {
                executionId,
                nodeName,
                error,
            });
            return;
        }
        const asBinary = true;
        pushInstance.send({
            type: 'nodeExecuteAfterData',
            data: { executionId, nodeName, itemCountByConnectionType, data: dataToSend },
        }, pushRef, asBinary);
    });
    hooks.addHandler('workflowExecuteBefore', async function (_workflow, data) {
        const { executionId } = this;
        const { id: workflowId, name: workflowName } = this.workflowData;
        logger.debug('Executing hook (hookFunctionsPush)', {
            executionId,
            pushRef,
            workflowId,
        });
        const user = await getUser();
        let runDataToStringify = {};
        const hasRunData = data?.resultData.runData && Object.keys(data.resultData.runData).length > 0;
        if (hasRunData && user) {
            try {
                const dummy = buildRedactableExecution(this, data.resultData.runData, data);
                const result = await redactionProxy.processExecution(dummy, {
                    user,
                    keepOriginal: true,
                });
                runDataToStringify =
                    result !== dummy ? result.data.resultData.runData : data.resultData.runData;
            }
            catch (error) {
                logger.error('Failed to redact execution start data, sending empty runData', {
                    executionId,
                    workflowId,
                    error,
                });
            }
        }
        else if (hasRunData && !user) {
            logger.warn('Cannot redact execution start data: unable to resolve user', {
                executionId,
                workflowId,
                userId,
            });
        }
        pushInstance.send({
            type: 'executionStarted',
            data: {
                executionId,
                mode: this.mode,
                startedAt: new Date(),
                retryOf,
                workflowId,
                workflowName,
                flattedRunData: (0, flatted_1.stringify)(runDataToStringify),
            },
        }, pushRef);
    });
    hooks.addHandler('workflowExecuteAfter', function (fullRunData) {
        const { executionId } = this;
        const { id: workflowId } = this.workflowData;
        logger.debug('Executing hook (hookFunctionsPush)', {
            executionId,
            pushRef,
            workflowId,
        });
        const { status } = fullRunData;
        if (status === 'waiting') {
            pushInstance.send({ type: 'executionWaiting', data: { executionId } }, pushRef);
        }
        else {
            pushInstance.send({ type: 'executionFinished', data: { executionId, workflowId, status } }, pushRef);
        }
    });
}
function hookFunctionsExternalHooks(hooks) {
    const externalHooks = di_1.Container.get(external_hooks_1.ExternalHooks);
    hooks.addHandler('workflowExecuteBefore', async function (workflow) {
        await externalHooks.run('workflow.preExecute', [workflow, this.mode]);
    });
    hooks.addHandler('workflowExecuteAfter', async function (fullRunData) {
        await externalHooks.run('workflow.postExecute', [
            fullRunData,
            this.workflowData,
            this.executionId,
        ]);
    });
}
function hookFunctionsSaveProgress(hooks, { saveSettings }) {
    if (!saveSettings.progress)
        return;
    hooks.addHandler('nodeExecuteAfter', async function (nodeName, data, executionData) {
        await (0, save_execution_progress_1.saveExecutionProgress)(this.workflowData.id, this.executionId, nodeName, data, executionData);
    });
}
function hookFunctionsFinalizeExecutionStatus(hooks) {
    hooks.addHandler('workflowExecuteAfter', (fullRunData) => {
        fullRunData.status = (0, shared_hook_functions_1.determineFinalExecutionStatus)(fullRunData);
    });
}
function hookFunctionsStatistics(hooks) {
    const workflowStatisticsService = di_1.Container.get(workflow_statistics_service_1.WorkflowStatisticsService);
    hooks.addHandler('nodeFetchedData', (workflowId, node) => {
        workflowStatisticsService.emit('nodeFetchedData', { workflowId, node });
    });
}
async function duplicateBinaryDataToParent(fullRunData, parentExecution, binaryDataService) {
    const outputData = (0, workflow_helpers_1.getDataLastExecutedNodeData)(fullRunData);
    if (outputData?.data?.main) {
        const duplicatedData = await binaryDataService.duplicateBinaryData(n8n_core_1.FileLocation.ofExecution(parentExecution.workflowId, parentExecution.executionId), outputData.data.main);
        outputData.data.main = duplicatedData;
    }
}
function hookFunctionsSave(hooks, { pushRef, retryOf, saveSettings, parentExecution }) {
    const logger = di_1.Container.get(backend_common_1.Logger);
    const errorReporter = di_1.Container.get(n8n_core_1.ErrorReporter);
    const executionRepository = di_1.Container.get(db_1.ExecutionRepository);
    const executionPersistence = di_1.Container.get(execution_persistence_1.ExecutionPersistence);
    const binaryDataService = di_1.Container.get(n8n_core_1.BinaryDataService);
    const workflowStaticDataService = di_1.Container.get(workflow_static_data_service_1.WorkflowStaticDataService);
    const workflowStatisticsService = di_1.Container.get(workflow_statistics_service_1.WorkflowStatisticsService);
    hooks.addHandler('workflowExecuteAfter', async function (fullRunData, newStaticData) {
        logger.debug('Executing hook (hookFunctionsSave)', {
            executionId: this.executionId,
            workflowId: this.workflowData.id,
        });
        await (0, restore_binary_data_id_1.restoreBinaryDataId)(fullRunData, this.executionId, this.mode);
        if (parentExecution) {
            await duplicateBinaryDataToParent(fullRunData, parentExecution, binaryDataService);
        }
        const isManualMode = this.mode === 'manual';
        try {
            if (!isManualMode && (0, utils_1.isWorkflowIdValid)(this.workflowData.id) && newStaticData) {
                try {
                    await workflowStaticDataService.saveStaticDataById(this.workflowData.id, newStaticData);
                }
                catch (e) {
                    errorReporter.error(e);
                    logger.error(`There was a problem saving the workflow with id "${this.workflowData.id}" to save changed staticData: "${e.message}" (hookFunctionsSave)`, { executionId: this.executionId, workflowId: this.workflowData.id });
                }
            }
            if (isManualMode && !saveSettings.manual && !fullRunData.waitTill) {
                await executionRepository.softDelete(this.executionId);
                return;
            }
            const shouldNotSave = (fullRunData.status === 'success' && !saveSettings.success) ||
                (fullRunData.status !== 'success' && !saveSettings.error);
            if (shouldNotSave && !fullRunData.waitTill && !isManualMode) {
                (0, execute_error_workflow_1.executeErrorWorkflow)(this.workflowData, fullRunData, this.mode, this.executionId, retryOf);
                await executionPersistence.deleteInFlightExecution({
                    workflowId: this.workflowData.id,
                    executionId: this.executionId,
                    storedAt: fullRunData.storedAt,
                });
                return;
            }
            const fullExecutionData = (0, shared_hook_functions_1.prepareExecutionDataForDbUpdate)({
                runData: fullRunData,
                workflowData: this.workflowData,
                workflowStatusFinal: fullRunData.status,
                retryOf,
            });
            if (fullRunData.waitTill && isManualMode) {
                fullExecutionData.data.pushRef = pushRef;
            }
            await (0, shared_hook_functions_1.updateExistingExecution)({
                executionId: this.executionId,
                workflowId: this.workflowData.id,
                executionData: fullExecutionData,
            });
            await (0, shared_hook_functions_1.updateExistingExecutionMetadata)(this.executionId, fullRunData.data?.resultData?.metadata);
            if (!isManualMode) {
                (0, execute_error_workflow_1.executeErrorWorkflow)(this.workflowData, fullRunData, this.mode, this.executionId, retryOf);
            }
        }
        finally {
            workflowStatisticsService.emit('workflowExecutionCompleted', {
                workflowData: this.workflowData,
                fullRunData,
            });
        }
    });
}
function hookFunctionsSaveWorker(hooks, { pushRef, retryOf }) {
    const logger = di_1.Container.get(backend_common_1.Logger);
    const errorReporter = di_1.Container.get(n8n_core_1.ErrorReporter);
    const workflowStaticDataService = di_1.Container.get(workflow_static_data_service_1.WorkflowStaticDataService);
    const workflowStatisticsService = di_1.Container.get(workflow_statistics_service_1.WorkflowStatisticsService);
    hooks.addHandler('workflowExecuteAfter', async function (fullRunData, newStaticData) {
        logger.debug('Executing hook (hookFunctionsSaveWorker)', {
            executionId: this.executionId,
            workflowId: this.workflowData.id,
        });
        const isManualMode = this.mode === 'manual';
        try {
            if (!isManualMode && (0, utils_1.isWorkflowIdValid)(this.workflowData.id) && newStaticData) {
                try {
                    await workflowStaticDataService.saveStaticDataById(this.workflowData.id, newStaticData);
                }
                catch (e) {
                    errorReporter.error(e);
                    logger.error(`There was a problem saving the workflow with id "${this.workflowData.id}" to save changed staticData: "${e.message}" (workflowExecuteAfter)`, { workflowId: this.workflowData.id });
                }
            }
            if (!isManualMode && fullRunData.status !== 'success' && fullRunData.status !== 'waiting') {
                (0, execute_error_workflow_1.executeErrorWorkflow)(this.workflowData, fullRunData, this.mode, this.executionId, retryOf);
            }
            const fullExecutionData = (0, shared_hook_functions_1.prepareExecutionDataForDbUpdate)({
                runData: fullRunData,
                workflowData: this.workflowData,
                workflowStatusFinal: fullRunData.status,
                retryOf,
            });
            if (fullRunData.waitTill && isManualMode) {
                fullExecutionData.data.pushRef = pushRef;
            }
            await (0, shared_hook_functions_1.updateExistingExecution)({
                executionId: this.executionId,
                workflowId: this.workflowData.id,
                executionData: fullExecutionData,
            });
        }
        finally {
            workflowStatisticsService.emit('workflowExecutionCompleted', {
                workflowData: this.workflowData,
                fullRunData,
            });
        }
    });
}
function getLifecycleHooksForSubExecutions(mode, executionId, workflowData, userId, parentExecution, projectId, projectName) {
    const hooks = new n8n_core_1.ExecutionLifecycleHooks(mode, executionId, workflowData);
    const saveSettings = (0, to_save_settings_1.toSaveSettings)(workflowData.settings);
    hookFunctionsWorkflowEvents(hooks, userId, projectId, projectName);
    hookFunctionsNodeEvents(hooks);
    hookFunctionsFinalizeExecutionStatus(hooks);
    hookFunctionsSave(hooks, { saveSettings, parentExecution });
    hookFunctionsSaveProgress(hooks, { saveSettings });
    hookFunctionsStatistics(hooks);
    hookFunctionsExternalHooks(hooks);
    return hooks;
}
function getLifecycleHooksForScalingWorker(data, executionId) {
    const { pushRef, retryOf, executionMode, workflowData } = data;
    const hooks = new n8n_core_1.ExecutionLifecycleHooks(executionMode, executionId, workflowData, retryOf ?? undefined);
    const saveSettings = (0, to_save_settings_1.toSaveSettings)(workflowData.settings);
    const optionalParameters = { pushRef, retryOf: retryOf ?? undefined, saveSettings };
    hookFunctionsNodeEvents(hooks);
    hookFunctionsFinalizeExecutionStatus(hooks);
    hookFunctionsSaveWorker(hooks, optionalParameters);
    hookFunctionsSaveProgress(hooks, optionalParameters);
    hookFunctionsStatistics(hooks);
    hookFunctionsExternalHooks(hooks);
    if (executionMode === 'manual' && di_1.Container.get(n8n_core_1.InstanceSettings).isWorker) {
        hookFunctionsPush(hooks, optionalParameters, data.userId);
    }
    di_1.Container.get(ModulesHooksRegistry).addHooks(hooks);
    return hooks;
}
function getLifecycleHooksForScalingMain(data, executionId) {
    const { pushRef, retryOf, executionMode, workflowData, userId, projectId, projectName } = data;
    const hooks = new n8n_core_1.ExecutionLifecycleHooks(executionMode, executionId, workflowData, retryOf ?? undefined);
    const saveSettings = (0, to_save_settings_1.toSaveSettings)(workflowData.settings);
    const optionalParameters = { pushRef, retryOf: retryOf ?? undefined, saveSettings };
    const executionRepository = di_1.Container.get(db_1.ExecutionRepository);
    const executionPersistence = di_1.Container.get(execution_persistence_1.ExecutionPersistence);
    hookFunctionsWorkflowEvents(hooks, userId, projectId, projectName);
    hookFunctionsSaveProgress(hooks, optionalParameters);
    hookFunctionsExternalHooks(hooks);
    hookFunctionsFinalizeExecutionStatus(hooks);
    hooks.addHandler('workflowExecuteAfter', async function (fullRunData) {
        if (!fullRunData.finished)
            return;
        const isManualMode = this.mode === 'manual';
        if (isManualMode && !saveSettings.manual && !fullRunData.waitTill) {
            await executionRepository.softDelete(this.executionId);
            return;
        }
        const shouldNotSave = (fullRunData.status === 'success' && !saveSettings.success) ||
            (fullRunData.status !== 'success' && !saveSettings.error);
        if (!isManualMode && shouldNotSave && !fullRunData.waitTill) {
            await executionPersistence.deleteInFlightExecution({
                workflowId: this.workflowData.id,
                executionId: this.executionId,
                storedAt: fullRunData.storedAt,
            });
        }
        else {
            await (0, shared_hook_functions_1.updateExistingExecutionMetadata)(this.executionId, fullRunData.data?.resultData?.metadata);
        }
    });
    hooks.handlers.nodeExecuteBefore = [];
    hooks.handlers.nodeExecuteAfter = [];
    di_1.Container.get(ModulesHooksRegistry).addHooks(hooks);
    return hooks;
}
function getLifecycleHooksForRegularMain(data, executionId) {
    const { pushRef, retryOf, executionMode, workflowData, userId, projectId, projectName } = data;
    const hooks = new n8n_core_1.ExecutionLifecycleHooks(executionMode, executionId, workflowData, retryOf ?? undefined);
    const saveSettings = (0, to_save_settings_1.toSaveSettings)(workflowData.settings);
    const optionalParameters = { pushRef, retryOf: retryOf ?? undefined, saveSettings };
    hookFunctionsWorkflowEvents(hooks, userId, projectId, projectName);
    hookFunctionsNodeEvents(hooks);
    hookFunctionsFinalizeExecutionStatus(hooks);
    hookFunctionsSave(hooks, optionalParameters);
    hookFunctionsPush(hooks, optionalParameters, userId);
    hookFunctionsSaveProgress(hooks, optionalParameters);
    hookFunctionsStatistics(hooks);
    hookFunctionsExternalHooks(hooks);
    di_1.Container.get(ModulesHooksRegistry).addHooks(hooks);
    return hooks;
}
//# sourceMappingURL=execution-lifecycle-hooks.js.map