Source: engine/universal/core/src/engine/shouldActivateFlowNode.js

const { handle5thIndustryUserTask } = require('./5thIndustry.js');
const {
  getProcessIds,
  getDefinitionsAndProcessIdForEveryCallActivity,
} = require('@proceed/bpmn-helper');
const distribution = require('@proceed/distribution');

/**
 * Creates a callback function that can be used to register to the userTask stream of the neo engine
 *
 * @param {Object} engine proceed engine instance that contains the process information
 * @param {Object} instance the process instance the user task was encountered in
 */
function onUserTask(engine, instance, tokenId, userTask) {
  return new Promise(async (resolve) => {
    engine._log.info({
      msg: `A new User Task was encountered, InstanceId = ${instance.id}`,
      instanceId: instance.id,
    });

    function activate() {
      resolve(true);
    }

    const extendedUserTask = {
      processInstance: instance,
      processChain: engine.processID,
      tokenId,
      ...userTask,
      attrs: userTask.$attrs || {},
      activate,
    };

    if (userTask.implementation === '5thIndustry') {
      const success = await handle5thIndustryUserTask(extendedUserTask, engine);

      if (!success) {
        return;
      }

      activate();
    }

    engine.userTasks.push(extendedUserTask);
  });
}

/**
 * Creates a callback that handles the execution of callActivities when one becomes active
 *
 * @param {Object} engine proceed engine instance that contains the process information
 * @param {Class} Engine the process Execution class that we want to create a new Instance of to execute the callActivity process
 * @param {Object} instance the process instance the call activity was encountered in
 */
function onCallActivity(engine, Engine, instance, tokenId, callActivity) {
  return new Promise(async (resolve) => {
    // get necessary process information about the process referenced by the callActivity
    const callActivityDefinitionIdMapping = await getDefinitionsAndProcessIdForEveryCallActivity(
      engine._bpmn
    );

    const callActivityDefinitionId = callActivityDefinitionIdMapping[callActivity.id].definitionId;
    const importBPMN = await distribution.db.getImportedProcess(
      engine.definitionId,
      callActivityDefinitionId
    );

    const [importProcessId] = await getProcessIds(importBPMN);

    // if there is no execution engine registered for the process to execute create a new one
    if (!Object.keys(engine.callActivityExecutors).includes(importProcessId)) {
      const CAExecutionEngine = new Engine();
      engine.callActivityExecutors[importProcessId] = CAExecutionEngine;
      CAExecutionEngine._log = engine._log;

      await CAExecutionEngine.deployProcess(engine.definitionId, callActivityDefinitionId);
    }

    // get current variable state to pass on to the instance of the callActivity process
    const variables = instance.getVariables();

    // start execution of callActivity process with variables from the current instance
    engine._log.info({
      msg: `Starting callActivity with id ${callActivity.id}. Imported process definitionId: ${callActivityDefinitionId}. CallingInstanceId = ${instance.id}`,
      instanceId: instance.id,
    });

    engine.callActivityExecutors[importProcessId].startProcess(
      variables,
      undefined,
      // onStarted callBack: log that we started an instance of a callActivity process
      (callActivityInstance) => {
        callActivityInstance.callingInstance = instance;
        resolve(true);
      },
      // onEnded callBack: return possibly changed variables from the callActivity instance back to the calling instance
      (callActivityInstance) => {
        const variables = callActivityInstance.getVariables();

        instance.completeActivity(callActivity.id, tokenId, variables);
      }
    );
  });
}

module.exports = {
  getShouldActivateFlowNode(engine, Engine) {
    return async function shouldActivateFlowNode(
      processId,
      processInstanceId,
      tokenId,
      flowNode,
      state
    ) {
      const instance = engine.getInstance(processInstanceId);

      // flowNodes that are set to external should be handled through the Neo Engine API
      if (flowNode.$attrs && flowNode.$attrs['proceed:external']) {
        instance.updateToken(tokenId, { currentFlowNodeIsExternal: true });
        return false;
      }

      if (flowNode.$type === 'bpmn:UserTask') {
        await onUserTask(engine, instance, tokenId, flowNode);
      }

      if (flowNode.$type === 'bpmn:CallActivity') {
        await onCallActivity(engine, Engine, instance, tokenId, flowNode);
      }

      return true;
    };
  },
};