2023-01-27 03:22:44 -08:00
import type { IHookFunctions , IWebhookFunctions } from 'n8n-core' ;
2019-06-23 03:35:23 -07:00
2023-01-27 03:22:44 -08:00
import type {
2019-06-23 03:35:23 -07:00
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 ,
2019-06-23 03:35:23 -07:00
} from 'n8n-workflow' ;
2023-01-27 03:22:44 -08:00
import { NodeOperationError } from 'n8n-workflow' ;
2019-06-23 03:35:23 -07:00
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 : {
feat(editor): Node creator actions (#4696)
* WIP: Node Actions List UI
* WIP: Recommended Actions and preseting of fields
* WIP: Resource category
* :art: Moved actions categorisation to the server
* :label: Add missing INodeAction type
* :sparkles: Improve SSR categorisation, fix adding of mixed actions
* :recycle: Refactor CategorizedItems to composition api, style fixes
* WIP: Adding multiple nodes
* :recycle: Refactor rest of the NodeCreator component to composition API, conver globalLinkActions to composable
* :sparkles: Allow actions dragging, fix search and refactor passing of actions to categorized items
* :lipstick: Fix node actions title
* Migrate to the pinia store, add posthog feature and various fixes
* :bug: Fix filtering of trigger actions when not merged
* fix: N8N-5439 — Do not use simple node item when at NodeHelperPanel root
* :bug: Design review fixes
* :bug: Fix disabling of merged actions
* Fix trigger root filtering
* :sparkles: Allow for custom node actions parser, introduce hubspot parser
* :bug: Fix initial node params validation, fix position of second added node
* :bug: Introduce operations category, removed canvas node names overrride, fix API actions display and prevent dragging of action nodes
* :sparkles: Prevent NDV auto-open feature flag
* :bug: Inject recommened action for trigger nodes without actions
* Refactored NodeCreatorNode to Storybook, change filtering of merged nodes for the trigger helper panel, minor fixes
* Improve rendering of app nodes and animation
* Cleanup, any only enable accordion transition on triggerhelperpanel
* Hide node creator scrollbars in Firefox
* Minor styles fixes
* Do not copy the array in rendering method
* Removed unused props
* Fix memory leak
* Fix categorisation of regular nodes with a single resource
* Implement telemetry calls for node actions
* Move categorization to FE
* Fix client side actions categorisation
* Skip custom action show
* Only load tooltip for NodeIcon if necessary
* Fix lodash startCase import
* Remove lodash.startcase
* Cleanup
* Fix node creator autofocus on "tab"
* Prevent posthog getFeatureFlag from crashing
* Debugging preview env search issues
* Remove logs
* Make sure the pre-filled params are update not overwritten
* Get rid of transition in itemiterator
* WIP: Rough version of NodeActions keyboard navigation, replace nodeCreator composable with Pinia store module
* Rewrite to add support for ActionItem to ItemIterator and make CategorizedItems accept items props
* Fix category item counter & cleanup
* Add APIHint to actions search no-result, clean up NodeCreatorNode
* Improve node actions no results message
* Remove logging, fix filtering of recommended placeholder category
* Remove unused NodeActions component and node merging feature falg
* Do not show regular nodes without actions
* Make sure to add manual trigger when adding http node via actions hint
* Fixed api hint footer line height
* Prevent pointer-events od NodeIcon img and remove "this" from template
* Address PR points
* Fix e2e specs
* Make sure canvas ia loaded
* Make sure canvas ia loaded before opening nodeCreator in e2e spec
* Fix flaky workflows tags e2e getter
* Imrpove node creator click outside UX, add manual node to regular nodes added from trigger panel
* Add manual trigger node if dragging regular from trigger panel
2022-12-09 01:56:36 -08: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 ;
2022-12-29 03:20:43 -08:00
const endpoint = '/webhooks' ;
2019-06-23 03:35:23 -07:00
const body = {
resource ,
target : webhookUrl ,
} ;
2022-12-02 12:54:28 -08:00
const responseData = await asanaApiRequest . call ( this , 'POST' , endpoint , body ) ;
2020-09-16 09:20:27 -07:00
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 > {
2022-12-02 12:54:28 -08:00
const bodyData = this . getBodyData ( ) ;
2019-06-23 03:35:23 -07:00
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 {
2023-02-27 19:39:43 -08:00
workflowData : [ this . helpers . returnJsonArray ( req . body . events as IDataObject [ ] ) ] ,
2019-06-23 03:35:23 -07:00
} ;
}
}