2019-06-23 03:35:23 -07:00
import {
IWebhookData ,
WebhookHttpMethod ,
2020-01-22 15:06:43 -08:00
Workflow ,
2021-03-23 11:08:47 -07:00
WorkflowActivateMode ,
2019-06-23 03:35:23 -07:00
WorkflowExecuteMode ,
} from 'n8n-workflow' ;
import {
NodeExecuteFunctions ,
} from './' ;
export class ActiveWebhooks {
private workflowWebhooks : {
[ key : string ] : IWebhookData [ ] ;
} = { } ;
private webhookUrls : {
2021-01-23 11:00:32 -08:00
[ key : string ] : IWebhookData [ ] ;
2019-06-23 03:35:23 -07:00
} = { } ;
testWebhooks = false ;
/ * *
* Adds a new webhook
*
* @param { IWebhookData } webhookData
* @param { WorkflowExecuteMode } mode
* @returns { Promise < void > }
* @memberof ActiveWebhooks
* /
2021-03-23 11:08:47 -07:00
async add ( workflow : Workflow , webhookData : IWebhookData , mode : WorkflowExecuteMode , activation : WorkflowActivateMode ) : Promise < void > {
2020-01-22 15:06:43 -08:00
if ( workflow . id === undefined ) {
2019-06-23 03:35:23 -07:00
throw new Error ( 'Webhooks can only be added for saved workflows as an id is needed!' ) ;
}
2021-02-09 00:14:40 -08:00
if ( webhookData . path . endsWith ( '/' ) ) {
webhookData . path = webhookData . path . slice ( 0 , - 1 ) ;
}
2019-06-23 03:35:23 -07:00
2021-01-23 11:00:32 -08:00
const webhookKey = this . getWebhookKey ( webhookData . httpMethod , webhookData . path , webhookData . webhookId ) ;
2020-05-30 16:03:58 -07:00
//check that there is not a webhook already registed with that path/method
2021-01-23 11:00:32 -08:00
if ( this . webhookUrls [ webhookKey ] && ! webhookData . webhookId ) {
2020-05-31 12:13:45 -07:00
throw new Error ( ` Test-Webhook can not be activated because another one with the same method " ${ webhookData . httpMethod } " and path " ${ webhookData . path } " is already active! ` ) ;
2020-05-30 16:03:58 -07:00
}
2020-01-22 15:06:43 -08:00
if ( this . workflowWebhooks [ webhookData . workflowId ] === undefined ) {
this . workflowWebhooks [ webhookData . workflowId ] = [ ] ;
2019-06-23 03:35:23 -07:00
}
// Make the webhook available directly because sometimes to create it successfully
// it gets called
2021-01-23 11:00:32 -08:00
if ( ! this . webhookUrls [ webhookKey ] ) {
this . webhookUrls [ webhookKey ] = [ ] ;
}
this . webhookUrls [ webhookKey ] . push ( webhookData ) ;
2019-06-23 03:35:23 -07:00
2020-08-27 04:37:17 -07:00
try {
2021-03-23 11:08:47 -07:00
const webhookExists = await workflow . runWebhookMethod ( 'checkExists' , webhookData , NodeExecuteFunctions , mode , activation , this . testWebhooks ) ;
2020-10-21 08:50:23 -07:00
if ( webhookExists !== true ) {
2020-08-27 04:37:17 -07:00
// If webhook does not exist yet create it
2021-03-23 11:08:47 -07:00
await workflow . runWebhookMethod ( 'create' , webhookData , NodeExecuteFunctions , mode , activation , this . testWebhooks ) ;
2020-07-24 04:56:20 -07:00
}
2020-08-27 04:37:17 -07:00
} catch ( error ) {
// If there was a problem unregister the webhook again
2021-01-23 11:00:32 -08:00
if ( this . webhookUrls [ webhookKey ] . length <= 1 ) {
delete this . webhookUrls [ webhookKey ] ;
} else {
this . webhookUrls [ webhookKey ] = this . webhookUrls [ webhookKey ] . filter ( webhook = > webhook . path !== webhookData . path ) ;
}
2019-06-23 03:35:23 -07:00
2020-08-27 04:37:17 -07:00
throw error ;
}
2020-01-22 15:06:43 -08:00
this . workflowWebhooks [ webhookData . workflowId ] . push ( webhookData ) ;
2019-06-23 03:35:23 -07:00
}
/ * *
* Returns webhookData if a webhook with matches is currently registered
*
* @param { WebhookHttpMethod } httpMethod
* @param { string } path
2021-01-23 11:00:32 -08:00
* @param { ( string | undefined ) } webhookId
2019-06-23 03:35:23 -07:00
* @returns { ( IWebhookData | undefined ) }
* @memberof ActiveWebhooks
* /
2021-01-23 11:00:32 -08:00
get ( httpMethod : WebhookHttpMethod , path : string , webhookId? : string ) : IWebhookData | undefined {
const webhookKey = this . getWebhookKey ( httpMethod , path , webhookId ) ;
2019-06-23 03:35:23 -07:00
if ( this . webhookUrls [ webhookKey ] === undefined ) {
return undefined ;
}
2021-02-09 00:14:40 -08:00
let webhook : IWebhookData | undefined ;
let maxMatches = 0 ;
const pathElementsSet = new Set ( path . split ( '/' ) ) ;
// check if static elements match in path
// if more results have been returned choose the one with the most static-route matches
this . webhookUrls [ webhookKey ] . forEach ( dynamicWebhook = > {
const staticElements = dynamicWebhook . path . split ( '/' ) . filter ( ele = > ! ele . startsWith ( ':' ) ) ;
const allStaticExist = staticElements . every ( staticEle = > pathElementsSet . has ( staticEle ) ) ;
if ( allStaticExist && staticElements . length > maxMatches ) {
maxMatches = staticElements . length ;
webhook = dynamicWebhook ;
2021-01-23 11:00:32 -08:00
}
2021-02-09 00:14:40 -08:00
// handle routes with no static elements
else if ( staticElements . length === 0 && ! webhook ) {
webhook = dynamicWebhook ;
}
} ) ;
2021-01-23 11:00:32 -08:00
return webhook ;
2019-06-23 03:35:23 -07:00
}
2020-07-24 07:24:18 -07:00
/ * *
* Gets all request methods associated with a single webhook
2020-07-24 07:43:23 -07:00
* @param path
2020-07-24 07:24:18 -07:00
* /
getWebhookMethods ( path : string ) : string [ ] {
2020-07-24 07:43:23 -07:00
const methods : string [ ] = [ ] ;
2020-07-24 07:24:18 -07:00
Object . keys ( this . webhookUrls )
. filter ( key = > key . includes ( path ) )
. map ( key = > {
methods . push ( key . split ( '|' ) [ 0 ] ) ;
} ) ;
return methods ;
}
2019-06-23 03:35:23 -07:00
2020-01-22 15:06:43 -08:00
/ * *
* Returns the ids of all the workflows which have active webhooks
*
* @returns { string [ ] }
* @memberof ActiveWebhooks
* /
getWorkflowIds ( ) : string [ ] {
return Object . keys ( this . workflowWebhooks ) ;
}
2019-06-23 03:35:23 -07:00
/ * *
* Returns key to uniquely identify a webhook
*
* @param { WebhookHttpMethod } httpMethod
* @param { string } path
2021-01-23 11:00:32 -08:00
* @param { ( string | undefined ) } webhookId
2019-06-23 03:35:23 -07:00
* @returns { string }
* @memberof ActiveWebhooks
* /
2021-01-23 11:00:32 -08:00
getWebhookKey ( httpMethod : WebhookHttpMethod , path : string , webhookId? : string ) : string {
if ( webhookId ) {
if ( path . startsWith ( webhookId ) ) {
const cutFromIndex = path . indexOf ( '/' ) + 1 ;
path = path . slice ( cutFromIndex ) ;
}
return ` ${ httpMethod } | ${ webhookId } | ${ path . split ( '/' ) . length } ` ;
}
2019-06-23 03:35:23 -07:00
return ` ${ httpMethod } | ${ path } ` ;
}
/ * *
* Removes all webhooks of a workflow
*
2020-01-22 15:06:43 -08:00
* @param { Workflow } workflow
2019-06-23 03:35:23 -07:00
* @returns { boolean }
* @memberof ActiveWebhooks
* /
2020-01-22 15:06:43 -08:00
async removeWorkflow ( workflow : Workflow ) : Promise < boolean > {
const workflowId = workflow . id ! . toString ( ) ;
2019-06-23 03:35:23 -07:00
if ( this . workflowWebhooks [ workflowId ] === undefined ) {
// If it did not exist then there is nothing to remove
return false ;
}
const webhooks = this . workflowWebhooks [ workflowId ] ;
const mode = 'internal' ;
// Go through all the registered webhooks of the workflow and remove them
for ( const webhookData of webhooks ) {
2021-03-23 11:08:47 -07:00
await workflow . runWebhookMethod ( 'delete' , webhookData , NodeExecuteFunctions , mode , 'update' , this . testWebhooks ) ;
2019-06-23 03:35:23 -07:00
2021-01-23 11:00:32 -08:00
delete this . webhookUrls [ this . getWebhookKey ( webhookData . httpMethod , webhookData . path , webhookData . webhookId ) ] ;
2019-06-23 03:35:23 -07:00
}
// Remove also the workflow-webhook entry
delete this . workflowWebhooks [ workflowId ] ;
return true ;
}
/ * *
2020-10-21 08:50:23 -07:00
* Removes all the webhooks of the given workflows
2019-06-23 03:35:23 -07:00
* /
2020-01-22 15:06:43 -08:00
async removeAll ( workflows : Workflow [ ] ) : Promise < void > {
2019-06-23 03:35:23 -07:00
const removePromises = [ ] ;
2020-01-22 15:06:43 -08:00
for ( const workflow of workflows ) {
removePromises . push ( this . removeWorkflow ( workflow ) ) ;
2019-06-23 03:35:23 -07:00
}
await Promise . all ( removePromises ) ;
return ;
}
}