2022-08-01 13:47:55 -07:00
import { IHookFunctions , IWebhookFunctions } from 'n8n-core' ;
2019-06-23 03:35:23 -07:00
import {
IDataObject ,
2020-09-16 09:20:27 -07:00
ILoadOptionsFunctions ,
INodePropertyOptions ,
2019-06-23 03:35:23 -07:00
INodeType ,
2020-10-01 05:01:39 -07:00
INodeTypeDescription ,
2019-10-11 04:02:44 -07:00
IWebhookResponseData ,
2021-04-16 09:33:36 -07:00
NodeOperationError ,
2019-06-23 03:35:23 -07:00
} from 'n8n-workflow' ;
2022-08-01 13:47:55 -07:00
import { asanaApiRequest , getWorkspaces } from './GenericFunctions' ;
2019-06-23 03:35:23 -07:00
2020-10-01 05:01:39 -07:00
// import {
// createHmac,
// } from 'crypto';
2019-06-23 03:35:23 -07:00
export class AsanaTrigger implements INodeType {
description : INodeTypeDescription = {
displayName : 'Asana Trigger' ,
name : 'asanaTrigger' ,
2021-03-25 09:10:02 -07:00
icon : 'file:asana.svg' ,
2019-06-23 03:35:23 -07:00
group : [ 'trigger' ] ,
version : 1 ,
2021-05-29 11:50:41 -07:00
description : 'Starts the workflow when Asana events occur.' ,
2019-06-23 03:35:23 -07:00
defaults : {
2020-04-29 10:41:16 -07:00
name : 'Asana-Trigger' ,
2019-06-23 03:35:23 -07:00
} ,
inputs : [ ] ,
outputs : [ 'main' ] ,
credentials : [
{
name : 'asanaApi' ,
required : true ,
2020-09-16 09:20:27 -07:00
displayOptions : {
show : {
2022-08-01 13:47:55 -07:00
authentication : [ 'accessToken' ] ,
2020-09-16 09:20:27 -07:00
} ,
} ,
} ,
{
name : 'asanaOAuth2Api' ,
required : true ,
displayOptions : {
show : {
2022-08-01 13:47:55 -07:00
authentication : [ 'oAuth2' ] ,
2020-09-16 09:20:27 -07:00
} ,
} ,
} ,
2019-06-23 03:35:23 -07:00
] ,
webhooks : [
{
name : 'default' ,
httpMethod : 'POST' ,
2019-08-28 08:16:09 -07:00
responseMode : 'onReceived' ,
2019-06-23 03:35:23 -07:00
path : 'webhook' ,
} ,
] ,
properties : [
2020-09-16 09:20:27 -07:00
{
displayName : 'Authentication' ,
name : 'authentication' ,
type : 'options' ,
options : [
{
name : 'Access Token' ,
value : 'accessToken' ,
} ,
{
name : 'OAuth2' ,
value : 'oAuth2' ,
} ,
] ,
default : 'accessToken' ,
} ,
2019-06-23 03:35:23 -07:00
{
displayName : 'Resource' ,
name : 'resource' ,
type : 'string' ,
default : '' ,
required : true ,
description : 'The resource ID to subscribe to. The resource can be a task or project.' ,
} ,
2019-11-27 17:20:15 -08:00
{
2022-06-03 10:23:49 -07:00
displayName : 'Workspace Name or ID' ,
2019-11-27 17:20:15 -08:00
name : 'workspace' ,
2020-09-16 09:20:27 -07:00
type : 'options' ,
typeOptions : {
loadOptionsMethod : 'getWorkspaces' ,
} ,
options : [ ] ,
2019-11-27 17:20:15 -08:00
default : '' ,
2022-08-01 13:47:55 -07:00
description :
'The workspace ID the resource is registered under. This is only required if you want to allow overriding existing webhooks. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.' ,
2019-11-27 17:20:15 -08:00
} ,
2019-06-23 03:35:23 -07:00
] ,
2020-09-16 09:20:27 -07:00
} ;
2019-06-23 03:35:23 -07:00
2020-09-16 09:20:27 -07:00
methods = {
loadOptions : {
// Get all the available workspaces to display them to user so that he can
// select them easily
async getWorkspaces ( this : ILoadOptionsFunctions ) : Promise < INodePropertyOptions [ ] > {
const workspaces = await getWorkspaces . call ( this ) ;
workspaces . unshift ( {
name : '' ,
value : '' ,
} ) ;
return workspaces ;
} ,
} ,
2019-06-23 03:35:23 -07:00
} ;
// @ts-ignore (because of request)
webhookMethods = {
default : {
async checkExists ( this : IHookFunctions ) : Promise < boolean > {
const webhookData = this . getWorkflowStaticData ( 'node' ) ;
2020-09-16 09:20:27 -07:00
const webhookUrl = this . getNodeWebhookUrl ( 'default' ) as string ;
2019-06-23 03:35:23 -07:00
2020-09-16 09:20:27 -07:00
const resource = this . getNodeParameter ( 'resource' ) as string ;
2019-06-23 03:35:23 -07:00
2020-09-16 09:20:27 -07:00
const workspace = this . getNodeParameter ( 'workspace' ) as string ;
2019-06-23 03:35:23 -07:00
2020-09-16 09:20:27 -07:00
const endpoint = '/webhooks' ;
const { data } = await asanaApiRequest . call ( this , 'GET' , endpoint , { } , { workspace } ) ;
2019-06-23 03:35:23 -07:00
2020-09-16 09:20:27 -07:00
for ( const webhook of data ) {
if ( webhook . resource . gid === resource && webhook . target === webhookUrl ) {
webhookData . webhookId = webhook . gid ;
return true ;
}
2019-06-23 03:35:23 -07:00
}
// If it did not error then the webhook exists
2020-09-16 09:20:27 -07:00
return false ;
2019-06-23 03:35:23 -07:00
} ,
async create ( this : IHookFunctions ) : Promise < boolean > {
2020-09-16 09:20:27 -07:00
const webhookData = this . getWorkflowStaticData ( 'node' ) ;
2020-04-29 10:41:16 -07:00
const webhookUrl = this . getNodeWebhookUrl ( 'default' ) as string ;
if ( webhookUrl . includes ( '%20' ) ) {
2022-08-01 13:47:55 -07:00
throw new NodeOperationError (
this . getNode ( ) ,
'The name of the Asana Trigger Node is not allowed to contain any spaces!' ,
) ;
2020-04-29 10:41:16 -07:00
}
2019-06-23 03:35:23 -07:00
const resource = this . getNodeParameter ( 'resource' ) as string ;
2020-09-16 09:20:27 -07:00
const endpoint = ` /webhooks ` ;
2019-06-23 03:35:23 -07:00
const body = {
resource ,
target : webhookUrl ,
} ;
2019-11-30 09:05:34 -08:00
let responseData ;
2019-06-23 03:35:23 -07:00
2020-09-16 09:20:27 -07:00
responseData = await asanaApiRequest . call ( this , 'POST' , endpoint , body ) ;
if ( responseData . data === undefined || responseData . data . gid === undefined ) {
2019-06-23 03:35:23 -07:00
// Required data is missing so was not successful
return false ;
}
2020-09-16 09:20:27 -07:00
webhookData . webhookId = responseData . data . gid as string ;
2019-06-23 03:35:23 -07:00
return true ;
} ,
async delete ( this : IHookFunctions ) : Promise < boolean > {
const webhookData = this . getWorkflowStaticData ( 'node' ) ;
if ( webhookData . webhookId !== undefined ) {
2020-09-16 09:20:27 -07:00
const endpoint = ` /webhooks/ ${ webhookData . webhookId } ` ;
2019-06-23 03:35:23 -07:00
const body = { } ;
try {
await asanaApiRequest . call ( this , 'DELETE' , endpoint , body ) ;
2021-04-16 09:33:36 -07:00
} catch ( error ) {
2019-06-23 03:35:23 -07:00
return false ;
}
// Remove from the static workflow data so that it is clear
// that no webhooks are registred anymore
delete webhookData . webhookId ;
delete webhookData . webhookEvents ;
2020-09-16 14:57:27 -07:00
delete webhookData . hookSecret ;
2019-06-23 03:35:23 -07:00
}
return true ;
} ,
} ,
} ;
2019-10-11 04:02:44 -07:00
async webhook ( this : IWebhookFunctions ) : Promise < IWebhookResponseData > {
2019-06-23 03:35:23 -07:00
const bodyData = this . getBodyData ( ) as IDataObject ;
const headerData = this . getHeaderData ( ) as IDataObject ;
const req = this . getRequestObject ( ) ;
2020-09-16 09:20:27 -07:00
const webhookData = this . getWorkflowStaticData ( 'node' ) ;
2019-06-23 03:35:23 -07:00
if ( headerData [ 'x-hook-secret' ] !== undefined ) {
// Is a create webhook confirmation request
webhookData . hookSecret = headerData [ 'x-hook-secret' ] ;
const res = this . getResponseObject ( ) ;
res . set ( 'X-Hook-Secret' , webhookData . hookSecret as string ) ;
res . status ( 200 ) . end ( ) ;
2020-09-16 09:20:27 -07:00
2019-06-23 03:35:23 -07:00
return {
noWebhookResponse : true ,
} ;
}
// Is regular webhook call
// Check if it contains any events
2022-08-01 13:47:55 -07:00
if (
bodyData . events === undefined ||
! Array . isArray ( bodyData . events ) ||
bodyData . events . length === 0
) {
2019-06-23 03:35:23 -07:00
// Does not contain any event data so nothing to process so no reason to
// start the workflow
return { } ;
}
2020-09-16 14:57:27 -07:00
// TODO: Had to be deactivated as it is currently not possible to get the secret
// in production mode as the static data overwrites each other because the
// two exist at the same time (create webhook [with webhookId] and receive
// webhook [with secret])
// // Check if the request is valid
// // (if the signature matches to data and hookSecret)
// const computedSignature = createHmac('sha256', webhookData.hookSecret as string).update(JSON.stringify(req.body)).digest('hex');
// if (headerData['x-hook-signature'] !== computedSignature) {
// // Signature is not valid so ignore call
// return {};
// }
2019-06-23 03:35:23 -07:00
return {
2022-08-01 13:47:55 -07:00
workflowData : [ this . helpers . returnJsonArray ( req . body . events ) ] ,
2019-06-23 03:35:23 -07:00
} ;
}
}