UNPKG

flexbiz-server

Version:

Flexible Server

65 lines (64 loc) 33.8 kB
const nodeHandlers=require("./handlers"),crypto=require("crypto"); class FlowEngine{constructor(){this.runningFlows={};this.webhookManager=null}setWebhookManager($manager$$){$manager$$?(Logger.info("[FlowEngine] WebhookManager instance injected."),this.webhookManager=$manager$$):Logger.warn("[FlowEngine] Attempted to set a null or undefined WebhookManager.")}async startFlow($flowDefinition_flowState_flow_id$$,$initialData$$,$flowInstanceId$$,$begin_node_potentialStartNodes_startNodeIdOverride$$=null){const $flowIdStr$$=$flowDefinition_flowState_flow_id$$.toString(); if(!global.mongoose.Types.ObjectId.isValid($flowDefinition_flowState_flow_id$$))throw Error(`Flow ID "${$flowDefinition_flowState_flow_id$$}" is not valid`);$flowDefinition_flowState_flow_id$$=await global.getModel("flow").findOne({_id:$flowDefinition_flowState_flow_id$$}).lean();if(!$flowDefinition_flowState_flow_id$$)throw Error(`Flow ID "${$flowIdStr$$}" does not exist`);const $nodes$$={};var $edges_finalStatus_startNodeType$$=$flowDefinition_flowState_flow_id$$.edges||[];const $inDegrees$$={}; $edges_finalStatus_startNodeType$$.forEach($edge$$=>{$edge$$.target&&($inDegrees$$[$edge$$.target]=($inDegrees$$[$edge$$.target]||0)+1)});($flowDefinition_flowState_flow_id$$.nodes||[]).forEach($node$$=>{$node$$&&$node$$.id?$nodes$$[$node$$.id]={...$node$$,status:$node$$.data?.disabled?"disabled":"pending",predecessorCount:$inDegrees$$[$node$$.id]||0,incomingPayloads:{},completedPredecessors:new Set,outputData:void 0,inputPayload:void 0}:Logger.warn("Node definition without ID found, skipping:",$node$$)}); if($begin_node_potentialStartNodes_startNodeIdOverride$$&&$nodes$$[$begin_node_potentialStartNodes_startNodeIdOverride$$])$begin_node_potentialStartNodes_startNodeIdOverride$$=$nodes$$[$begin_node_potentialStartNodes_startNodeIdOverride$$];else{const $targetNodes$$=new Set($edges_finalStatus_startNodeType$$.map($e$$=>$e$$.target));$begin_node_potentialStartNodes_startNodeIdOverride$$=Object.values($nodes$$).filter($n$$=>!$targetNodes$$.has($n$$.id));if($begin_node_potentialStartNodes_startNodeIdOverride$$.length=== 0&&Object.keys($nodes$$).length>0)throw Error(`[${$flowInstanceId$$}] No start node found for Flow ${$flowIdStr$$}.`);if($begin_node_potentialStartNodes_startNodeIdOverride$$.length>1)Logger.warn(`[${$flowInstanceId$$}] Multiple start nodes found. Using first: ${$begin_node_potentialStartNodes_startNodeIdOverride$$[0].id}`),$begin_node_potentialStartNodes_startNodeIdOverride$$=$begin_node_potentialStartNodes_startNodeIdOverride$$[0];else if($begin_node_potentialStartNodes_startNodeIdOverride$$.length=== 1)$begin_node_potentialStartNodes_startNodeIdOverride$$=$begin_node_potentialStartNodes_startNodeIdOverride$$[0];else throw Error(`[${$flowInstanceId$$}] Flow ${$flowIdStr$$} has no nodes.`);}if(!$begin_node_potentialStartNodes_startNodeIdOverride$$)throw Error(`[${$flowInstanceId$$}] Cannot start Flow ${$flowIdStr$$}: Start node could not be determined.`);if($begin_node_potentialStartNodes_startNodeIdOverride$$?.data?.disabled)throw Error("Node kh\u1edfi \u0111\u1ea7u \u0111\u00e3 b\u1ecb v\u00f4 hi\u1ec7u ho\u00e1"); var $abortController_isWebhookStart$$=new AbortController;$flowDefinition_flowState_flow_id$$={flowId:$flowIdStr$$,instanceId:$flowInstanceId$$,status:"initializing",initialData:$initialData$$,nodes:$nodes$$,edges:$edges_finalStatus_startNodeType$$,flowDefinition:$flowDefinition_flowState_flow_id$$,abortController:$abortController_isWebhookStart$$,abortSignal:$abortController_isWebhookStart$$.signal,triggerDownstreamCallback:($sourceNodeId$$,$payload$$)=>{this.triggerDownstreamNodesInternal($flowInstanceId$$, $sourceNodeId$$,$payload$$)}};this.runningFlows[$flowInstanceId$$]=$flowDefinition_flowState_flow_id$$;Logger.info(`[${$flowInstanceId$$}] Start node is ${$begin_node_potentialStartNodes_startNodeIdOverride$$.id} - ${$begin_node_potentialStartNodes_startNodeIdOverride$$.name} - ${$begin_node_potentialStartNodes_startNodeIdOverride$$.type}`);$edges_finalStatus_startNodeType$$=$begin_node_potentialStartNodes_startNodeIdOverride$$.type;$abortController_isWebhookStart$$=$edges_finalStatus_startNodeType$$=== "webhook"||$edges_finalStatus_startNodeType$$==="facebook";if(this.webhookManager){const $webhookNodes$$=Object.values($flowDefinition_flowState_flow_id$$.nodes).filter($n$$=>($n$$.type==="webhook"||$n$$.type==="facebook")&&$n$$.data?.path&&!$n$$.data?.disabled);if($webhookNodes$$.length>0){Logger.info(`[${$flowInstanceId$$}] Registering webhooks for flow ${$flowIdStr$$}...`);try{await Promise.all($webhookNodes$$.map(async $whNode$$=>{let {path:$path$$,method:$method$$,verificationMethod:$verificationMethod$$, verificationChallengeQueryParam:$verificationChallengeQueryParam$$,verificationTokenQueryParam:$verificationTokenQueryParam$$,verificationTokenValue:$verificationTokenValue$$,appSecret:$appSecret$$}=$whNode$$.data;const $nodeId$$=$whNode$$.id;var $fbVerifyToken_verificationConfig$$=null;let $dataMethod$$=$method$$;if($whNode$$.type==="facebook"){$dataMethod$$="POST";({verifyTokenValue:$fbVerifyToken_verificationConfig$$}=$whNode$$.data);$appSecret$$=$whNode$$.data.appSecret;if(!$fbVerifyToken_verificationConfig$$){Logger.error(`[${$flowInstanceId$$}] Skipping FB node ${$nodeId$$}: Missing 'verifyTokenValue'.`); return}$fbVerifyToken_verificationConfig$$={method:"GET",challengeQueryParam:"hub.challenge",tokenParam:"hub.verify_token",tokenValue:$fbVerifyToken_verificationConfig$$}}else $verificationChallengeQueryParam$$&&($fbVerifyToken_verificationConfig$$={method:$verificationMethod$$||"GET",challengeQueryParam:$verificationChallengeQueryParam$$,...($verificationTokenQueryParam$$&&$verificationTokenValue$$&&{tokenParam:$verificationTokenQueryParam$$,tokenValue:$verificationTokenValue$$})});$dataMethod$$= $dataMethod$$?.toLowerCase()||"post";await this.webhookManager.registerWebhook($path$$,$dataMethod$$,$flowIdStr$$,$nodeId$$,$flowInstanceId$$,$fbVerifyToken_verificationConfig$$,$appSecret$$);Logger.info(`[${$flowInstanceId$$}] Registered webhook: ${$dataMethod$$.toUpperCase()} ${$path$$} (Node ${$nodeId$$})`)}))}catch($registerError$$){throw Logger.error(`[${$flowInstanceId$$}] Error during webhook registration:`,$registerError$$),this.cleanupFlow($flowIdStr$$,$flowInstanceId$$),Error(`Failed during webhook registration: ${$registerError$$.message}`); }}}else $abortController_isWebhookStart$$&&Logger.warn(`[${$flowInstanceId$$}] Webhook start node detected but WebhookManager is not set.`);$abortController_isWebhookStart$$?($edges_finalStatus_startNodeType$$="listening",$flowDefinition_flowState_flow_id$$.status=$edges_finalStatus_startNodeType$$,Logger.info(`[${$flowInstanceId$$}] Flow instance is LISTENING for webhook trigger at node ${$begin_node_potentialStartNodes_startNodeIdOverride$$.id}. Status: ${$edges_finalStatus_startNodeType$$}`),global.socketContainer?.socketIO&& global.socketContainer.socketIO.to($flowInstanceId$$).emit("flow-started",{flowInstanceId:$flowInstanceId$$,flowId:$flowIdStr$$,status:$edges_finalStatus_startNodeType$$})):($edges_finalStatus_startNodeType$$=$edges_finalStatus_startNodeType$$==="event"?"listening":"running",$flowDefinition_flowState_flow_id$$.status=$edges_finalStatus_startNodeType$$,Logger.info(`[${$flowInstanceId$$}] Flow ${$flowIdStr$$} instance starting nodes immediately. Status: ${$edges_finalStatus_startNodeType$$}`),global.socketContainer?.socketIO&& global.socketContainer.socketIO.to($flowInstanceId$$).emit("flow-started",{flowInstanceId:$flowInstanceId$$,flowId:$flowIdStr$$,status:$edges_finalStatus_startNodeType$$}),this.runNode($begin_node_potentialStartNodes_startNodeIdOverride$$,$initialData$$??{},$flowInstanceId$$,"initial").catch($error$$=>{Logger.error(`[${$flowInstanceId$$}] Critical error during initial node run:`,$error$$);this.stopFlow($flowInstanceId$$,!0,`Critical error starting flow: ${$error$$.message}`)}));await global.getModel("flow").updateOne({_id:$flowIdStr$$}, {activeInstance:{status:$edges_finalStatus_startNodeType$$,initialData:$initialData$$,instanceId:$flowInstanceId$$}});return{flowInstanceId:$flowInstanceId$$,status:$edges_finalStatus_startNodeType$$}}async runNode($node$$,$errorMsg$jscomp$1_payload$$,$flowInstanceId$$,$triggerId$$=null){const $triggerContext$$=$triggerId$$?`|${$triggerId$$}`:"";Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] --- runNode ENTERED for node ${$node$$?.id} - ${$node$$.name} - ${$node$$.type} ---`);var $errorMsg_flowState$$= this.runningFlows[$flowInstanceId$$];if($errorMsg_flowState$$&&["running","listening"].includes($errorMsg_flowState$$.status))if($node$$&&$node$$.id&&$errorMsg_flowState$$.nodes[$node$$.id]){$errorMsg_flowState$$.status==="listening"&&(Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] runNode: Transitioning flow status from 'listening' to 'running'.`),$errorMsg_flowState$$.status="running",global.socketContainer?.socketIO&&global.socketContainer.socketIO.to($flowInstanceId$$).emit("flow-status-updated", {flowInstanceId:$flowInstanceId$$,status:"running"}));var $currentNodeState$$=$errorMsg_flowState$$.nodes[$node$$.id];if($errorMsg_flowState$$.abortSignal.aborted)Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] runNode: Node ${$node$$.id} aborted before execution.`),this.updateNodeStatus($flowInstanceId$$,$node$$.id,"cancelled",void 0,void 0,$triggerId$$),this.checkFlowCompletion($flowInstanceId$$);else{Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] ---> Attempting to set node ${$node$$.id} to RUNNING`); this.updateNodeStatus($flowInstanceId$$,$node$$.id,"running",void 0,$errorMsg$jscomp$1_payload$$,$triggerId$$);Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] Status of node ${$node$$.id} AFTER update attempt: ${$errorMsg_flowState$$.nodes[$node$$.id]?.status}`);Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] Running node ${$node$$.id} (Type: ${$node$$.type})`);var $handler$$=nodeHandlers[$node$$.type]||nodeHandlers.debug;$handler$$||(Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] Don't find handler for type node ${$node$$.type}. Use debug node...`), $handler$$=nodeHandlers.debug);if($handler$$)try{Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] Executing handler for node ${$node$$.id}...`);const $engineContext$$={updateNodeStatus:this.updateNodeStatus.bind(this),triggerDownstreamNodesInternal:this.triggerDownstreamNodesInternal.bind(this),runNode:this.runNode.bind(this),cancelBranch:this._cancelBranch.bind(this)},$outputData$$=await $handler$$($errorMsg_flowState$$,$currentNodeState$$,$errorMsg$jscomp$1_payload$$,$errorMsg_flowState$$.abortSignal, $engineContext$$);Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] Handler for node ${$node$$.id} finished.`);if(this.runningFlows[$flowInstanceId$$]&&["running","listening"].includes(this.runningFlows[$flowInstanceId$$].status))if($errorMsg_flowState$$.abortSignal.aborted)Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] Node ${$node$$.id} aborted during handler execution.`),this.updateNodeStatus($flowInstanceId$$,$node$$.id,"cancelled",void 0,void 0,$triggerId$$),this.checkFlowCompletion($flowInstanceId$$); else if($outputData$$===void 0&&["switch","foreachitem"].includes($node$$.type))Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] ${$node$$.type} node ${$node$$.id} handled branching. Setting status to completed.`),Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] ---> Attempting to set node ${$node$$.id} to COMPLETED (after branching)`),this.updateNodeStatus($flowInstanceId$$,$node$$.id,"completed",void 0,void 0,$triggerId$$),this.checkFlowCompletion($flowInstanceId$$);else if($outputData$$&& typeof $outputData$$==="object"&&$outputData$$.repeating===!0){const $nodeStatus$$=$outputData$$.nodeStatus||"active_repeating";Logger.info(`[${$flowInstanceId$$}] Node ${$node$$.id} is now an active '${$outputData$$.nodeType||"repeating"}' node (independent of trigger ${$triggerId$$}).`);Logger.info(`[${$flowInstanceId$$}] ---> Attempting to set node ${$node$$.id} to ${$nodeStatus$$} (repeating)`);this.updateNodeStatus($flowInstanceId$$,$node$$.id,$nodeStatus$$,void 0,void 0,null)}else Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] ---> Attempting to set node ${$node$$.id} to COMPLETED`), this.updateNodeStatus($flowInstanceId$$,$node$$.id,"completed",$outputData$$,void 0,$triggerId$$),Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] Node ${$node$$.id} completed normally.`),Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] Triggering downstream nodes from ${$node$$.id}...`),this.triggerDownstreamNodesInternal($flowInstanceId$$,$node$$.id,$outputData$$,$triggerId$$);else Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] Flow stopped or changed status during node ${$node$$.id} execution.`), this.checkFlowCompletion($flowInstanceId$$)}catch($error$$){$error$$ instanceof DOMException&&$error$$.name==="AbortError"?(Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] Node ${$node$$.id} execution aborted by signal.`),Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] ---> Attempting to set node ${$node$$.id} to CANCELLED (AbortError)`),this.updateNodeStatus($flowInstanceId$$,$node$$.id,"cancelled",void 0,void 0,$triggerId$$)):(Logger.error(`[${$flowInstanceId$$}${$triggerContext$$}] Node ${$node$$.id} failed during execution:`, $error$$),$errorMsg$jscomp$1_payload$$=$error$$.message||$error$$,Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] ---> Attempting to set node ${$node$$.id} to FAILED:`,$errorMsg$jscomp$1_payload$$),this.updateNodeStatus($flowInstanceId$$,$node$$.id,"failed",$errorMsg$jscomp$1_payload$$,void 0,$triggerId$$),Logger.warn(`[${$flowInstanceId$$}${$triggerContext$$}] Execution path failed for this trigger at node ${$node$$.id}.`)),Object.values($errorMsg_flowState$$.nodes).find($n$$=>$n$$.type=== "webhook"||$n$$.type==="facebook")||(Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}]: check flow completion`),this.checkFlowCompletion($flowInstanceId$$))}else $errorMsg_flowState$$=`No handler for type ${$node$$.type} or debug (Node ${$node$$.id}).`,Logger.error(`[${$flowInstanceId$$}${$triggerContext$$}] ${$errorMsg_flowState$$}`),this.updateNodeStatus($flowInstanceId$$,$node$$.id,"failed",$errorMsg_flowState$$,void 0,$triggerId$$),this.stopFlow($flowInstanceId$$,!0,$errorMsg_flowState$$)}}else Logger.error(`[${$flowInstanceId$$}${$triggerContext$$}] runNode ERROR: Invalid node object passed:`, $node$$),this.stopFlow($flowInstanceId$$,!0,`Invalid node ID: ${$node$$?.id}`);else $errorMsg_flowState$$?Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] runNode SKIPPED for node ${$node$$?.id}: Flow status is ${$errorMsg_flowState$$.status}.`):Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] runNode SKIPPED for node ${$node$$?.id}: Flow state not found.`)}triggerDownstreamNodesInternal($flowInstanceId$$,$sourceNodeId$$,$payload$$,$triggerId$$){const $flowState$$=this.runningFlows[$flowInstanceId$$], $triggerContext$$=$triggerId$$?`|${$triggerId$$}`:"";if(!$flowState$$||$flowState$$.status!=="running"&&$flowState$$.status!=="listening"||$flowState$$.abortSignal.aborted)Logger.info(`\u26a0\ufe0f [${$flowInstanceId$$}${$triggerContext$$}] Skipping downstream trigger from ${$sourceNodeId$$}. Reason: Flow status (${$flowState$$?.status}) not 'running or listening' or aborted (${$flowState$$.abortSignal.aborted}).`);else if($flowState$$.nodes[$sourceNodeId$$]){var $nextNodeIds$$=$flowState$$.edges.filter($edge$$=> $edge$$.source===$sourceNodeId$$).map($edge$$=>$edge$$.target).filter($id$$=>$flowState$$.nodes[$id$$]&&!$flowState$$.nodes[$id$$].data?.disabled);if($nextNodeIds$$.length>0){Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] Node ${$sourceNodeId$$} has ${$nextNodeIds$$.length} downstream nodes: ${$nextNodeIds$$.join(", ")}`);for(const $nextNodeId$$ of $nextNodeIds$$){const $targetNodeState$$=$flowState$$.nodes[$nextNodeId$$];if(!$targetNodeState$$||$targetNodeState$$.data?.disabled)Logger.warn(`\u26a0\ufe0f [${$flowInstanceId$$}${$triggerContext$$}] Edge target ${$nextNodeId$$} not found or disabled.`); else if($targetNodeState$$.incomingPayloads=$targetNodeState$$.incomingPayloads||{},$targetNodeState$$.completedPredecessors=$targetNodeState$$.completedPredecessors||new Set,$targetNodeState$$.incomingPayloads[$sourceNodeId$$]=$payload$$,$targetNodeState$$.completedPredecessors.add($sourceNodeId$$),Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] Node ${$sourceNodeId$$} -> ${$nextNodeId$$}. Join: ${$targetNodeState$$.completedPredecessors.size}/${$targetNodeState$$.predecessorCount}`),$targetNodeState$$.predecessorCount<= 0||$targetNodeState$$.completedPredecessors.size>=$targetNodeState$$.predecessorCount){$targetNodeState$$.predecessorCount<=0?Logger.warn(`[${$flowInstanceId$$}${$triggerContext$$}] Node ${$nextNodeId$$} triggered with 0 predecessors.`):Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] All predecessors for ${$nextNodeId$$} done. Triggering.`);let $combinedPayload$$;const $incomingPayloadValues$$=Object.values($targetNodeState$$.incomingPayloads);$targetNodeState$$.predecessorCount<=1?($combinedPayload$$= $incomingPayloadValues$$.length>0?$incomingPayloadValues$$[$incomingPayloadValues$$.length-1]:{},Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] Using single payload for node ${$nextNodeId$$}.`)):($combinedPayload$$=$incomingPayloadValues$$,Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] Combining ${$incomingPayloadValues$$.length} payloads into array for node ${$nextNodeId$$}.`));$targetNodeState$$.completedPredecessors.clear();$targetNodeState$$.incomingPayloads={};Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] ---> Attempting to set node ${$nextNodeId$$} to PENDING (before run)`); this.updateNodeStatus($flowInstanceId$$,$nextNodeId$$,"pending",void 0,void 0,null);Logger.info(`\ud83d\udd25 [${$flowInstanceId$$}${$triggerContext$$}] Calling runNode for ${$nextNodeId$$}...`);this.runNode($targetNodeState$$,$combinedPayload$$,$flowInstanceId$$,$triggerId$$).catch($error$$=>{Logger.error(`\u274c [${$flowInstanceId$$}${$triggerContext$$}] Error starting ${$nextNodeId$$}:`,$error$$);this.updateNodeStatus($flowInstanceId$$,$nextNodeId$$,"failed",$error$$.message||String($error$$), void 0,$triggerId$$)})}else Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] Node ${$nextNodeId$$} still waiting for predecessors.`)}}else Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] Node ${$sourceNodeId$$} is a terminal node for trigger ${$triggerId$$}.`);Object.values($flowState$$.nodes).find($n$$=>($n$$.type==="webhook"||$n$$.type==="facebook"||$n$$.type==="event")&&$n$$.predecessorCount===0)&&$flowState$$.status==="running"?Object.values($flowState$$.nodes).some($n$$=>$n$$.status=== "running")?Logger.info(`[${$flowInstanceId$$}] Path for trigger ${$triggerId$$} ended, but other nodes might be running. Keeping status 'running'.`):(Logger.info(`[${$flowInstanceId$$}] No nodes seem to be running. Setting status back to 'listening'.`),$flowState$$.status="listening",global.socketContainer?.socketIO&&global.socketContainer.socketIO.to($flowInstanceId$$).emit("flow-status-updated",{flowInstanceId:$flowInstanceId$$,status:"listening"})):$nextNodeIds$$.length==0?(Object.values($flowState$$.nodes).forEach($node$$=> {["pending"].includes($node$$.status)&&this.updateNodeStatus($flowInstanceId$$,$node$$.id,"cancelled",void 0,void 0,null)}),$flowState$$.status="completed",global.socketContainer?.socketIO&&global.socketContainer.socketIO.to($flowInstanceId$$).emit("flow-status-updated",{flowInstanceId:$flowInstanceId$$,status:"completed"})):this.checkFlowCompletion($flowInstanceId$$)}else Logger.warn(`\u26a0\ufe0f [${$flowInstanceId$$}${$triggerContext$$}] Source node ${$sourceNodeId$$} not found.`)}async triggerNodeInInstance($flowInstanceId$$, $targetNodeId$$,$payload$$){var $flowState$$=this.runningFlows[$flowInstanceId$$];if($flowState$$)if(["running","listening"].includes($flowState$$.status))if($flowState$$=$flowState$$.nodes[$targetNodeId$$],!$flowState$$||$flowState$$.data?.disabled)Logger.error(`[FlowEngine.triggerNode] Target node ${$targetNodeId$$} not found or disabled in instance ${$flowInstanceId$$}.`);else{var $triggerId$$=`trig_${Date.now()}_${crypto.randomBytes(4).toString("hex")}`,$triggerContext$$=`|${$triggerId$$}`;Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] Received external trigger for node ${$targetNodeId$$}. Resetting and running...`); Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] Resetting branch starting from ${$targetNodeId$$}...`);await this._resetBranchStatus($flowInstanceId$$,$targetNodeId$$,$triggerId$$);this.runNode($flowState$$,$payload$$??{},$flowInstanceId$$,$triggerId$$).catch($error$$=>{Logger.error(`[${$flowInstanceId$$}|${$triggerId$$}] Error re-running node ${$targetNodeId$$} from external trigger:`,$error$$);this.updateNodeStatus($flowInstanceId$$,$targetNodeId$$,"failed",$error$$.message||String($error$$), void 0,$triggerId$$)})}else Logger.warn(`[FlowEngine.triggerNode] Instance ${$flowInstanceId$$} is not in 'running' or 'listening' state (current: ${$flowState$$.status}). Ignoring trigger.`);else Logger.warn(`[FlowEngine.triggerNode] Instance ${$flowInstanceId$$} not found. Ignoring trigger.`)}checkFlowCompletion($flowInstanceId$$){const $flowState$$=this.runningFlows[$flowInstanceId$$];$flowState$$&&!["completed","failed","stopped","listening"].includes($flowState$$.status)&&(Object.values($flowState$$.nodes).find($n$$=> ($n$$.type==="webhook"||$n$$.type==="facebook")&&$n$$.predecessorCount===0)?Logger.info("start node l\u00e0 m\u1ed9t webhook. Flow webhook kh\u00f4ng t\u1ef1 ho\u00e0n th\u00e0nh"):Object.values($flowState$$.nodes).find($n$$=>$n$$.status=="failed")&&!Object.values($flowState$$.nodes).find($n$$=>$n$$.status=="active_repeating"||$n$$.status=="active_listening")?(Logger.info(`[${$flowInstanceId$$}] Flow ${$flowState$$.flowId} instance failed.`),$flowState$$.status="failed",global.socketContainer?.socketIO&& global.socketContainer.socketIO.to($flowInstanceId$$).emit("flow-failed",{flowInstanceId:$flowInstanceId$$,flowId:$flowState$$.flowId}),this.cleanupFlow($flowState$$.flowId,$flowInstanceId$$)):Object.values($flowState$$.nodes).every($node$$=>["completed","failed","cancelled","stopped","disabled"].includes($node$$.status))&&(Logger.info(`[${$flowInstanceId$$}] All nodes finalized. Flow ${$flowState$$.flowId} instance completed.`),$flowState$$.status="completed",global.socketContainer?.socketIO&&global.socketContainer.socketIO.to($flowInstanceId$$).emit("flow-completed", {flowInstanceId:$flowInstanceId$$,flowId:$flowState$$.flowId}),this.cleanupFlow($flowState$$.flowId,$flowInstanceId$$)))}stopFlow($flowInstanceId$$,$isError$$=!1,$reason$$="Flow stopped manually"){const $flowState$$=this.runningFlows[$flowInstanceId$$];var $originalFlowId_stoppableStates$$="running pending active_repeating active_listening waiting listening initializing".split(" ");if($flowState$$&&$originalFlowId_stoppableStates$$.includes($flowState$$.status)){$originalFlowId_stoppableStates$$= $flowState$$.flowId;const $finalStatus$$=$isError$$?"failed":"stopped";Logger.info(`[${$flowInstanceId$$}] Stopping flow instance for ${$originalFlowId_stoppableStates$$}. Reason: ${$finalStatus$$}. Msg: ${$reason$$}`);$flowState$$.abortController&&!$flowState$$.abortController.signal.aborted&&($flowState$$.abortController.abort(),Logger.info(`[${$flowInstanceId$$}] Abort signal sent.`));$flowState$$.status=$finalStatus$$;Object.values($flowState$$.nodes).forEach($node$$=>{["running","pending","active_repeating", "active_listening"].includes($node$$.status)&&this.updateNodeStatus($flowInstanceId$$,$node$$.id,"cancelled",void 0,void 0,null)});if(global.socketContainer?.socketIO){const $eventData$$={flowInstanceId:$flowInstanceId$$,flowId:$originalFlowId_stoppableStates$$};$isError$$?($eventData$$.error=$reason$$,global.socketContainer.socketIO.to($flowInstanceId$$).emit("flow-failed",$eventData$$)):global.socketContainer.socketIO.to($flowInstanceId$$).emit("flow-stopped",$eventData$$)}this._unregisterWebhooksForInstance($flowState$$); this.cleanupFlow($originalFlowId_stoppableStates$$,$flowInstanceId$$);Logger.info(`[${$flowInstanceId$$}] Flow instance finalized as ${$finalStatus$$}.`);return{...$flowState$$,status:$finalStatus$$}}return $flowState$$?$flowState$$:null}_unregisterWebhooksForInstance($dataMethod$jscomp$1_flowState$jscomp$6_webhookNodes$$){if(this.webhookManager&&$dataMethod$jscomp$1_flowState$jscomp$6_webhookNodes$$){var $flowInstanceId$$=$dataMethod$jscomp$1_flowState$jscomp$6_webhookNodes$$.instanceId;Logger.info(`[${$flowInstanceId$$}] Requesting unregistration of webhooks for flow ${$dataMethod$jscomp$1_flowState$jscomp$6_webhookNodes$$.flowId}...`); $dataMethod$jscomp$1_flowState$jscomp$6_webhookNodes$$=Object.values($dataMethod$jscomp$1_flowState$jscomp$6_webhookNodes$$.nodes).filter($n$$=>($n$$.type==="webhook"||$n$$.type==="facebook")&&$n$$.data?.path);for(const $whNode$$ of $dataMethod$jscomp$1_flowState$jscomp$6_webhookNodes$$){let {path:$path$$,method:$method$$,verificationMethod:$verificationMethod$$}=$whNode$$.data;$dataMethod$jscomp$1_flowState$jscomp$6_webhookNodes$$=$method$$;let $verifyMethod$$=$verificationMethod$$||"GET";$whNode$$.type=== "facebook"&&($dataMethod$jscomp$1_flowState$jscomp$6_webhookNodes$$="POST",$verifyMethod$$="GET");$dataMethod$jscomp$1_flowState$jscomp$6_webhookNodes$$=$dataMethod$jscomp$1_flowState$jscomp$6_webhookNodes$$?.toLowerCase()||"post";try{this.webhookManager.unregisterWebhook($path$$,$dataMethod$jscomp$1_flowState$jscomp$6_webhookNodes$$,$flowInstanceId$$,$verifyMethod$$.toLowerCase()),Logger.info(`[${$flowInstanceId$$}] Requested unregistration for webhook: ${$dataMethod$jscomp$1_flowState$jscomp$6_webhookNodes$$.toUpperCase()} ${$path$$} (Node ${$whNode$$.id})`)}catch($unregisterError$$){Logger.error(`[${$flowInstanceId$$}] Failed to unregister webhook ${$dataMethod$jscomp$1_flowState$jscomp$6_webhookNodes$$.toUpperCase()} ${$path$$} for node ${$whNode$$.id}:`, $unregisterError$$.message)}}}}_cancelBranch($flowInstanceId$$,$startNodeId$$,$triggerId$$,$visitedNodes$$=new Set){const $flowState$$=this.runningFlows[$flowInstanceId$$];if($flowState$$&&!$visitedNodes$$.has($startNodeId$$)){var $nodeToCancel$$=$flowState$$.nodes[$startNodeId$$];$nodeToCancel$$?($visitedNodes$$.add($startNodeId$$),$nodeToCancel$$.status==="pending"&&(Logger.info(`[${$flowInstanceId$$}${$triggerId$$?`|${$triggerId$$}`:""}] ---> Cancelling node ${$startNodeId$$} in unused branch.`), this.updateNodeStatus($flowInstanceId$$,$startNodeId$$,"cancelled",void 0,void 0,$triggerId$$),$flowState$$.edges.filter($edge$$=>$edge$$.source===$startNodeId$$).forEach($edge$$=>{this._cancelBranch($flowInstanceId$$,$edge$$.target,$triggerId$$,$visitedNodes$$)}))):Logger.warn(`[${$flowInstanceId$$}] _cancelBranch: Node ${$startNodeId$$} not found.`)}}async _resetBranchStatus($flowInstanceId$$,$startNodeId$$,$triggerId$$,$visitedNodes$$=new Set){var $flowState$jscomp$8_nextEdges$$=this.runningFlows[$flowInstanceId$$]; if($flowState$jscomp$8_nextEdges$$&&!$visitedNodes$$.has($startNodeId$$)){var $nodeToReset$$=$flowState$jscomp$8_nextEdges$$.nodes[$startNodeId$$];if($nodeToReset$$){$visitedNodes$$.add($startNodeId$$);$nodeToReset$$.status!=="pending"&&(Logger.info(`[${$flowInstanceId$$}${$triggerId$$?`|${$triggerId$$}`:""}] ---> Resetting node ${$startNodeId$$} to PENDING.`),this.updateNodeStatus($flowInstanceId$$,$startNodeId$$,"pending",void 0,void 0,null),$nodeToReset$$.incomingPayloads={},$nodeToReset$$.completedPredecessors= new Set);$flowState$jscomp$8_nextEdges$$=$flowState$jscomp$8_nextEdges$$.edges.filter($edge$$=>$edge$$.source===$startNodeId$$);for(const $edge$$ of $flowState$jscomp$8_nextEdges$$)await this._resetBranchStatus($flowInstanceId$$,$edge$$.target,$triggerId$$,$visitedNodes$$)}else Logger.warn(`[${$flowInstanceId$$}${$triggerId$$?`|${$triggerId$$}`:""}] _resetBranchStatus: Node ${$startNodeId$$} not found.`)}}_checkAndSetListening($flowInstanceId$$,$triggerContext$$){const $flowState$$=this.runningFlows[$flowInstanceId$$]; $flowState$$&&$flowState$$.status==="running"&&Object.values($flowState$$.nodes).find($n$$=>($n$$.type==="webhook"||$n$$.type==="facebook"||$n$$.type==="event")&&$n$$.predecessorCount===0)&&(Object.values($flowState$$.nodes).some($n$$=>$n$$.status==="running")?Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] Path ended, but other nodes might be running. Keeping status 'running'.`):(Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] No nodes seem to be running. Setting status back to 'listening'.`), $flowState$$.status="listening",global.socketContainer?.socketIO&&global.socketContainer.socketIO.to($flowInstanceId$$).emit("flow-status-updated",{flowInstanceId:$flowInstanceId$$,status:"listening"})))}async cleanupFlow($flowId$$,$flowInstanceId$$){if(this.runningFlows[$flowInstanceId$$]){const {status:$status$$,initialData:$initialData$$,instanceId:$instanceId$$}=this.runningFlows[$flowInstanceId$$];await global.getModel("flow").updateOne({_id:$flowId$$},{activeInstance:{status:$status$$,initialData:$initialData$$, instanceId:$instanceId$$}});Logger.info(`[${$flowInstanceId$$}] Cleaned up instance data.`)}}updateNodeStatus($flowInstanceId$$,$nodeId$$,$status$$,$logEventPayload_outputDataOrError$$,$errorMsg$$,$triggerId$$=null){var $flowState$$=this.runningFlows[$flowInstanceId$$];if($flowState$$?.nodes[$nodeId$$]){const $node$$=$flowState$$.nodes[$nodeId$$];$flowState$$=$node$$.status;const $triggerContext$$=$triggerId$$?`|${$triggerId$$}`:"";$flowState$$!==$status$$&&($node$$.status=$status$$);$status$$=== "completed"&&$logEventPayload_outputDataOrError$$!==void 0&&($node$$.outputData=$logEventPayload_outputDataOrError$$);$status$$==="running"&&$errorMsg$$!==void 0&&($node$$.inputPayload=$errorMsg$$);$status$$!=="completed"&&($node$$.outputData=void 0);$errorMsg$$=null;$status$$==="failed"?$errorMsg$$=$node$$.error=$logEventPayload_outputDataOrError$$:$node$$.error=void 0;$flowState$$!==$status$$&&global.socketContainer?.socketIO&&($logEventPayload_outputDataOrError$$={flowInstanceId:$flowInstanceId$$, triggerId:$triggerId$$||"initial",nodeId:$nodeId$$,nodeType:$node$$.type,status:$status$$,timestamp:Date.now(),...($status$$==="completed"&&{outputData:$logEventPayload_outputDataOrError$$}),...($status$$==="failed"&&{error:$errorMsg$$})},Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] Emitting flow-log-event:`,{...$logEventPayload_outputDataOrError$$,outputData:"..."}),global.socketContainer.socketIO.to($flowInstanceId$$).emit("flow-log-event",$logEventPayload_outputDataOrError$$));$flowState$$!== $status$$&&Logger.info(`[${$flowInstanceId$$}${$triggerContext$$}] UPDATED Node ${$nodeId$$} status: ${$flowState$$} -> ${$status$$}`)}else Logger.warn(`[${$flowInstanceId$$}] ### updateNodeStatus ### Cannot update node ${$nodeId$$}: Node or flow instance not found.`)}getFlowStatus($flowInstanceId$jscomp$12_flowState$$){return($flowInstanceId$jscomp$12_flowState$$=this.runningFlows[$flowInstanceId$jscomp$12_flowState$$])?{flowId:$flowInstanceId$jscomp$12_flowState$$.flowId,instanceId:$flowInstanceId$jscomp$12_flowState$$.instanceId, status:$flowInstanceId$jscomp$12_flowState$$.status,nodes:Object.values($flowInstanceId$jscomp$12_flowState$$.nodes).map($n$$=>({id:$n$$.id,type:$n$$.type,status:$n$$.status,outputData:$n$$.outputData,inputPayload:$n$$.inputPayload,error:$n$$.error}))}:null}getAllFlowStatuses(){return Object.keys(this.runningFlows).map($id$$=>this.getFlowStatus($id$$)).filter(Boolean)}getInstancesForFlow($flowId$$){const $targetFlowId$$=$flowId$$.toString();return Object.values(this.runningFlows).filter($state$$=> $state$$.flowId===$targetFlowId$$).map($state$$=>this.getFlowStatus($state$$.instanceId)).filter(Boolean)}stopAllInstancesOfFlow($flowId$$,$reason$$="Stopped all instances for flow definition"){const $targetFlowId$$=$flowId$$.toString();$flowId$$=Object.values(this.runningFlows).filter($state$$=>$state$$.flowId===$targetFlowId$$&&"running pending active_repeating active_listening waiting listening".split(" ").includes($state$$.status)).map($state$$=>$state$$.instanceId);if($flowId$$.length===0)return Logger.info(`[FlowEngine] No running/waiting/listening instances found for flow ${$targetFlowId$$} to stop.`), [];Logger.info(`[FlowEngine] Requesting stop for ${$flowId$$.length} instances of flow ${$targetFlowId$$}. Reason: ${$reason$$}`);const $stoppedInstanceIds$$=[];$flowId$$.forEach($instanceId$$=>{try{this.stopFlow($instanceId$$,!1,$reason$$),$stoppedInstanceIds$$.push($instanceId$$)}catch($error$$){Logger.error(`[FlowEngine] Error stopping instance ${$instanceId$$} for flow ${$targetFlowId$$}:`,$error$$)}});return $stoppedInstanceIds$$}} const WebhookManager=require("./WebhookManager"),init=async $activeInstance_app_flows$$=>{Logger.info("init FlowEngine...");var $flowEngine_flowInstanceId$$=global.flowEngine=new FlowEngine;const $webhookManager$$=new WebhookManager($flowEngine_flowInstanceId$$);$flowEngine_flowInstanceId$$.setWebhookManager($webhookManager$$);$webhookManager$$.attach($activeInstance_app_flows$$);$activeInstance_app_flows$$=await global.getModel("flow").find({"activeInstance.status":{$in:["running","listening"]}}).lean(); for(let $flow$$ of $activeInstance_app_flows$$)if($activeInstance_app_flows$$=$flow$$.activeInstance){$flowEngine_flowInstanceId$$=$activeInstance_app_flows$$.instanceId;try{await global.flowEngine.startFlow($flow$$._id.toString(),$activeInstance_app_flows$$.initialData||{},$flowEngine_flowInstanceId$$),Logger.info("[FlowEngin] restart flow",$flow$$.title,$flow$$._id)}catch($e$$){Logger.error("[FlowEngin] restart flow",$e$$)}}};module.exports=init;