import * as express from 'express' ; import { ActiveWebhooks } from 'n8n-core' ; import { IWebhookData , IWorkflowExecuteAdditionalData , WebhookHttpMethod , Workflow , WorkflowActivateMode , WorkflowExecuteMode , } from 'n8n-workflow' ; import { IResponseCallbackData , IWorkflowDb , Push , ResponseHelper , WebhookHelpers } from '.' ; const WEBHOOK_TEST_UNREGISTERED_HINT = `Click the 'Execute workflow' button on the canvas, then try again. (In test mode, the webhook only works for one call after you click this button)` ; export class TestWebhooks { private testWebhookData : { [ key ] : { sessionId ? : string ; timeout : NodeJS . Timeout ; workflowData : IWorkflowDb ; workflow : Workflow ; } ; } = { } ; private activeWebhooks : ActiveWebhooks | null = null ; constructor ( ) { this . activeWebhooks = new ActiveWebhooks ( ) ; this . activeWebhooks . testWebhooks = true ; } param { WebhookHttpMethod } httpMethod param { string } path param { express . Request } request param { express . Response } response returns { Promise < object > } memberof async callTestWebhook ( httpMethod , path , request , response ) { request . params = { } ; if ( path . endsWith ( '/' ) ) { path = path . slice ( 0 , - 1 ) ; } let webhookData = this . activeWebhooks ! . get ( httpMethod , path ) ; if ( webhookData === undefined ) { const pathElements = path . split ( '/' ) ; const webhookId = pathElements . shift ( ) ; webhookData = this . activeWebhooks ! . get ( httpMethod , pathElements . join ( '/' ) , webhookId ) ; if ( webhookData === undefined ) { throw new ResponseHelper . ResponseError ( `The requested webhook "${ httpMethod } ${ path }" is not registered.` , 404 , 404 , WEBHOOK_TEST_UNREGISTERED_HINT ) ; } path = webhookData . path ; path . split ( '/' ) . forEach ( ( ele , index ) => { if ( ele . startsWith ( ':' ) ) { request . params [ ele . slice ( 1 ) ] = pathElements [ index ] ; } } ) ; } const webhookKey = `${ this . activeWebhooks ! . getWebhookKey ( webhookData . httpMethod , webhookData . path , webhookData . webhookId ) }|${ webhookData . workflowId }` ; if ( this . testWebhookData [ webhookKey ] === undefined ) { throw new ResponseHelper . ResponseError ( `The requested webhook "${ httpMethod } ${ path }" is not registered.` , 404 , 404 , WEBHOOK_TEST_UNREGISTERED_HINT ) ; } const { workflow } = this . testWebhookData [ webhookKey ] ; const workflowStartNode = workflow . getNode ( webhookData . node ) ; if ( workflowStartNode === null ) { throw new ResponseHelper . ResponseError ( 'Could not find node to process webhook.' , 404 , 404 ) ; } return new Promise ( async ( resolve , reject ) => { try { const executionMode = 'manual' ; const executionId = await WebhookHelpers . executeWebhook ( workflow , webhookData ! , this . testWebhookData [ webhookKey ] . workflowData , workflowStartNode , executionMode , this . testWebhookData [ webhookKey ] . sessionId , undefined , undefined , request , response , ( error , data ) => { if ( error !== null ) { return reject ( error ) ; } resolve ( data ) ; } ) ; if ( executionId === undefined ) { return ; } if ( this . testWebhookData [ webhookKey ] . sessionId !== undefined ) { const pushInstance = Push . getInstance ( ) ; pushInstance . send ( 'testWebhookReceived' , { workflowId : webhookData ! . workflowId , executionId } , this . testWebhookData [ webhookKey ] . sessionId ) ; } } catch ( error ) { } clearTimeout ( this . testWebhookData [ webhookKey ] . timeout ) ; delete this . testWebhookData [ webhookKey ] ; this . activeWebhooks ! . removeWorkflow ( workflow ) ; } ) ; } param path async getWebhookMethods ( path ) { const webhookMethods = this . activeWebhooks ! . getWebhookMethods ( path ) ; if ( webhookMethods === undefined ) { throw new ResponseHelper . ResponseError ( `The requested webhook "${ path }" is not registered.` , 404 , 404 , WEBHOOK_TEST_UNREGISTERED_HINT ) ; } return webhookMethods ; } param { IWorkflowDb } workflowData param { Workflow } workflow returns { ( Promise < IExecutionDb | undefined > ) } memberof async needsWebhookData ( workflowData , workflow , additionalData , mode , activation , sessionId ? , destinationNode ? ) { const webhooks = WebhookHelpers . getWorkflowWebhooks ( workflow , additionalData , destinationNode , true ) ; if ( ! webhooks . find ( ( webhook ) => webhook . webhookDescription . restartWebhook !== true ) ) { return false ; } if ( workflow . id === undefined ) { throw new Error ( 'Webhooks can only be added for saved workflows as an id is needed!' ) ; } const timeout = setTimeout ( ( ) => { this . cancelTestWebhook ( workflowData . id . toString ( ) ) ; } , 120000 ) ; let key ; const activatedKey = [ ] ; for ( const webhookData of webhooks ) { key = `${ this . activeWebhooks ! . getWebhookKey ( webhookData . httpMethod , webhookData . path , webhookData . webhookId ) }|${ workflowData . id }` ; activatedKey . push ( key ) ; this . testWebhookData [ key ] = { sessionId , timeout , workflow , workflowData , } ; try { await this . activeWebhooks ! . add ( workflow , webhookData , mode , activation ) ; } catch ( error ) { activatedKey . forEach ( ( deleteKey ) => delete this . testWebhookData [ deleteKey ] ) ; await this . activeWebhooks ! . removeWorkflow ( workflow ) ; throw error ; } } return true ; } param { string } workflowId returns { boolean } memberof cancelTestWebhook ( workflowId ) { let foundWebhook = false ; for ( const webhookKey of Object . keys ( this . testWebhookData ) ) { const webhookData = this . testWebhookData [ webhookKey ] ; if ( webhookData . workflowData . id . toString ( ) !== workflowId ) { continue ; } clearTimeout ( this . testWebhookData [ webhookKey ] . timeout ) ; if ( this . testWebhookData [ webhookKey ] . sessionId !== undefined ) { try { const pushInstance = Push . getInstance ( ) ; pushInstance . send ( 'testWebhookDeleted' , { workflowId } , this . testWebhookData [ webhookKey ] . sessionId ) ; } catch ( error ) { } } const { workflow } = this . testWebhookData [ webhookKey ] ; delete this . testWebhookData [ webhookKey ] ; if ( ! foundWebhook ) { this . activeWebhooks ! . removeWorkflow ( workflow ) ; } foundWebhook = true ; } return foundWebhook ; } /**
     * Removes all the currently active test webhooks
     */ async removeAll ( ) { if ( this . activeWebhooks === null ) { return ; } let workflow ; const workflows = [ ] ; for ( const webhookKey of Object . keys ( this . testWebhookData ) ) { workflow = this . testWebhookData [ webhookKey ] . workflow ; workflows . push ( workflow ) ; } return this . activeWebhooks . removeAll ( workflows ) ; } } let testWebhooksInstance ; export function getInstance ( ) { if ( testWebhooksInstance === undefined ) { testWebhooksInstance = new TestWebhooks ( ) ; } return testWebhooksInstance ; }