2022-08-01 13:47:55 -07:00
import { createHmac } from 'crypto' ;
2023-01-27 03:22:44 -08:00
import type {
2023-03-09 09:13:15 -08:00
IHookFunctions ,
IWebhookFunctions ,
2023-01-27 03:22:44 -08:00
IDataObject ,
INodeType ,
INodeTypeDescription ,
IWebhookResponseData ,
} from 'n8n-workflow' ;
2024-08-29 06:55:53 -07:00
import { NodeConnectionType } from 'n8n-workflow' ;
2021-07-23 13:28:18 -07:00
import {
getAutomaticSecret ,
getEvents ,
mapResource ,
webexApiRequest ,
webexApiRequestAllItems ,
} from './GenericFunctions' ;
export class CiscoWebexTrigger implements INodeType {
description : INodeTypeDescription = {
2021-08-07 00:46:33 -07:00
displayName : 'Webex by Cisco Trigger' ,
2021-07-23 13:28:18 -07:00
name : 'ciscoWebexTrigger' ,
2022-06-20 07:54:01 -07:00
// eslint-disable-next-line n8n-nodes-base/node-class-description-icon-not-svg
2021-08-05 10:06:28 -07:00
icon : 'file:ciscoWebex.png' ,
2021-07-23 13:28:18 -07:00
group : [ 'trigger' ] ,
version : 1 ,
subtitle : '={{$parameter["resource"] + ":" + $parameter["event"]}}' ,
description : 'Starts the workflow when Cisco Webex events occur.' ,
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 : 'Webex by Cisco Trigger' ,
2021-07-23 13:28:18 -07:00
} ,
inputs : [ ] ,
2024-08-29 06:55:53 -07:00
outputs : [ NodeConnectionType . Main ] ,
2021-07-23 13:28:18 -07:00
credentials : [
{
name : 'ciscoWebexOAuth2Api' ,
required : true ,
} ,
] ,
webhooks : [
{
name : 'default' ,
httpMethod : 'POST' ,
responseMode : 'onReceived' ,
path : 'webhook' ,
} ,
] ,
properties : [
{
displayName : 'Resource' ,
name : 'resource' ,
type : 'options' ,
2022-05-20 14:47:24 -07:00
noDataExpression : true ,
2021-07-23 13:28:18 -07:00
options : [
2022-06-03 10:23:49 -07:00
{
name : '[All]' ,
value : 'all' ,
} ,
2021-07-23 13:28:18 -07:00
{
name : 'Attachment Action' ,
value : 'attachmentAction' ,
} ,
{
name : 'Meeting' ,
value : 'meeting' ,
} ,
{
name : 'Membership' ,
value : 'membership' ,
} ,
{
name : 'Message' ,
value : 'message' ,
} ,
// {
// name: 'Telephony Call',
// value: 'telephonyCall',
// },
{
name : 'Recording' ,
value : 'recording' ,
} ,
{
name : 'Room' ,
value : 'room' ,
} ,
] ,
default : 'meeting' ,
required : true ,
} ,
. . . getEvents ( ) ,
{
displayName : 'Resolve Data' ,
name : 'resolveData' ,
type : 'boolean' ,
displayOptions : {
show : {
2022-08-01 13:47:55 -07:00
resource : [ 'attachmentAction' ] ,
2021-07-23 13:28:18 -07:00
} ,
} ,
default : true ,
2022-06-20 07:54:01 -07:00
// eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether
2022-08-01 13:47:55 -07:00
description :
'By default the response only contain a reference to the data the user inputed. If this option gets activated, it will resolve the data automatically.' ,
2021-07-23 13:28:18 -07:00
} ,
{
displayName : 'Filters' ,
name : 'filters' ,
type : 'collection' ,
placeholder : 'Add Filter' ,
default : { } ,
options : [
{
displayName : 'Has Files' ,
name : 'hasFiles' ,
type : 'boolean' ,
displayOptions : {
show : {
2022-08-01 13:47:55 -07:00
'/resource' : [ 'message' ] ,
'/event' : [ 'created' , 'deleted' ] ,
2021-07-23 13:28:18 -07:00
} ,
} ,
default : false ,
2022-06-20 07:54:01 -07:00
description : 'Whether to limit to messages which contain file content attachments' ,
2021-07-23 13:28:18 -07:00
} ,
{
displayName : 'Is Locked' ,
name : 'isLocked' ,
type : 'boolean' ,
displayOptions : {
show : {
2022-08-01 13:47:55 -07:00
'/resource' : [ 'room' ] ,
'/event' : [ 'created' , 'updated' ] ,
2021-07-23 13:28:18 -07:00
} ,
} ,
default : false ,
2022-06-20 07:54:01 -07:00
description : 'Whether to limit to rooms that are locked' ,
2021-07-23 13:28:18 -07:00
} ,
{
displayName : 'Is Moderator' ,
name : 'isModerator' ,
type : 'boolean' ,
displayOptions : {
show : {
2022-08-01 13:47:55 -07:00
'/resource' : [ 'membership' ] ,
'/event' : [ 'created' , 'updated' , 'deleted' ] ,
2021-07-23 13:28:18 -07:00
} ,
} ,
default : false ,
2022-06-20 07:54:01 -07:00
description : 'Whether to limit to moderators of a room' ,
2021-07-23 13:28:18 -07:00
} ,
{
displayName : 'Mentioned People' ,
name : 'mentionedPeople' ,
type : 'string' ,
displayOptions : {
show : {
2022-08-01 13:47:55 -07:00
'/resource' : [ 'message' ] ,
'/event' : [ 'created' , 'deleted' ] ,
2021-07-23 13:28:18 -07:00
} ,
} ,
default : '' ,
2022-08-01 13:47:55 -07:00
description :
'Limit to messages which contain these mentioned people, by person ID; accepts me as a shorthand for your own person ID; separate multiple values with commas' ,
2021-07-23 13:28:18 -07:00
} ,
{
displayName : 'Message ID' ,
name : 'messageId' ,
type : 'string' ,
displayOptions : {
show : {
2022-08-01 13:47:55 -07:00
'/resource' : [ 'attachmentAction' ] ,
'/event' : [ 'created' ] ,
2021-07-23 13:28:18 -07:00
} ,
} ,
default : '' ,
description : 'Limit to a particular message, by ID' ,
} ,
{
displayName : 'Owned By' ,
name : 'ownedBy' ,
displayOptions : {
show : {
2022-08-01 13:47:55 -07:00
'/resource' : [ 'meeting' ] ,
2021-07-23 13:28:18 -07:00
} ,
} ,
type : 'string' ,
default : '' ,
} ,
{
displayName : 'Person Email' ,
name : 'personEmail' ,
type : 'string' ,
displayOptions : {
show : {
2022-08-01 13:47:55 -07:00
'/resource' : [ 'membership' ] ,
'/event' : [ 'created' , 'updated' , 'deleted' ] ,
2021-07-23 13:28:18 -07:00
} ,
} ,
default : '' ,
description : 'Limit to a particular person, by email' ,
} ,
{
displayName : 'Person Email' ,
name : 'personEmail' ,
type : 'string' ,
displayOptions : {
show : {
2022-08-01 13:47:55 -07:00
'/resource' : [ 'message' ] ,
'/event' : [ 'created' , 'deleted' ] ,
2021-07-23 13:28:18 -07:00
} ,
} ,
default : '' ,
description : 'Limit to a particular person, by email' ,
} ,
{
displayName : 'Person ID' ,
name : 'personId' ,
type : 'string' ,
displayOptions : {
show : {
2022-08-01 13:47:55 -07:00
'/resource' : [ 'attachmentAction' ] ,
'/event' : [ 'created' ] ,
2021-07-23 13:28:18 -07:00
} ,
} ,
default : '' ,
description : 'Limit to a particular person, by ID' ,
} ,
{
displayName : 'Person ID' ,
name : 'personId' ,
type : 'string' ,
displayOptions : {
show : {
2022-08-01 13:47:55 -07:00
'/resource' : [ 'membership' ] ,
'/event' : [ 'created' , 'updated' , 'deleted' ] ,
2021-07-23 13:28:18 -07:00
} ,
} ,
default : '' ,
description : 'Limit to a particular person, by ID' ,
} ,
{
displayName : 'Person ID' ,
name : 'personId' ,
type : 'string' ,
displayOptions : {
show : {
2022-08-01 13:47:55 -07:00
'/resource' : [ 'message' ] ,
'/event' : [ 'created' , 'deleted' ] ,
2021-07-23 13:28:18 -07:00
} ,
} ,
default : '' ,
description : 'Limit to a particular person, by ID' ,
} ,
{
displayName : 'Room ID' ,
name : 'roomId' ,
type : 'string' ,
displayOptions : {
show : {
2022-08-01 13:47:55 -07:00
'/resource' : [ 'attachmentAction' ] ,
'/event' : [ 'created' ] ,
2021-07-23 13:28:18 -07:00
} ,
} ,
default : '' ,
description : 'Limit to a particular room, by ID' ,
} ,
{
displayName : 'Room ID' ,
name : 'roomId' ,
type : 'string' ,
displayOptions : {
show : {
2022-08-01 13:47:55 -07:00
'/resource' : [ 'membership' ] ,
'/event' : [ 'created' , 'updated' , 'deleted' ] ,
2021-07-23 13:28:18 -07:00
} ,
} ,
default : '' ,
description : 'Limit to a particular room, by ID' ,
} ,
{
displayName : 'Room ID' ,
name : 'roomId' ,
type : 'string' ,
displayOptions : {
show : {
2022-08-01 13:47:55 -07:00
'/resource' : [ 'message' ] ,
'/event' : [ 'created' , 'updated' ] ,
2021-07-23 13:28:18 -07:00
} ,
} ,
default : '' ,
description : 'Limit to a particular room, by ID' ,
} ,
{
displayName : 'Room Type' ,
name : 'roomType' ,
type : 'options' ,
options : [
{
name : 'Direct' ,
value : 'direct' ,
} ,
{
name : 'Group' ,
value : 'group' ,
} ,
] ,
displayOptions : {
show : {
2022-08-01 13:47:55 -07:00
'/resource' : [ 'message' ] ,
'/event' : [ 'created' , 'deleted' ] ,
2021-07-23 13:28:18 -07:00
} ,
} ,
default : '' ,
2022-05-06 14:01:25 -07:00
description : 'Limit to a particular room type' ,
2021-07-23 13:28:18 -07:00
} ,
{
displayName : 'Type' ,
name : 'type' ,
type : 'options' ,
options : [
{
name : 'Direct' ,
value : 'direct' ,
} ,
{
name : 'Group' ,
value : 'group' ,
} ,
] ,
displayOptions : {
show : {
2022-08-01 13:47:55 -07:00
'/resource' : [ 'room' ] ,
'/event' : [ 'created' , 'updated' ] ,
2021-07-23 13:28:18 -07:00
} ,
} ,
default : '' ,
2022-05-06 14:01:25 -07:00
description : 'Limit to a particular room type' ,
2021-07-23 13:28:18 -07:00
} ,
// {
// displayName: 'Call Type',
// name: 'callType',
// type: 'options',
// options: [
// {
// name: 'Emergency',
// value: 'emergency',
// },
// {
// name: 'External',
// value: 'external',
// },
// {
// name: 'Location',
// value: 'location',
// },
// {
// name: 'Disconnected',
// value: 'disconnected',
// },
// {
// name: 'Organization',
// value: 'organization',
// },
// {
// name: 'Other',
// value: 'other',
// },
// {
// name: 'Repair',
// value: 'repair',
// },
// ],
// displayOptions: {
// show: {
// '/resource': [
// 'telephonyCall',
// ],
// '/event': [
// 'created',
// 'deleted',
// 'updated',
// ],
// },
// },
// default: '',
// description: `Limit to a particular call type`,
// },
// {
// displayName: 'Person ID',
// name: 'personId',
// type: 'string',
// displayOptions: {
// show: {
// '/resource': [
// 'telephonyCall',
// ],
// '/event': [
// 'created',
// 'deleted',
// 'updated',
// ],
// },
// },
// default: '',
// description: 'Limit to a particular person, by ID',
// },
// {
// displayName: 'Personality',
// name: 'personality',
// type: 'options',
// options: [
// {
// name: 'Click To Dial',
// value: 'clickToDial',
// },
// {
// name: 'Originator',
// value: 'originator',
// },
// {
// name: 'Terminator',
// value: 'terminator',
// },
// ],
// displayOptions: {
// show: {
// '/resource': [
// 'telephonyCall',
// ],
// '/event': [
// 'created',
// 'deleted',
// 'updated',
// ],
// },
// },
// default: '',
// description: `Limit to a particular call personality`,
// },
// {
// displayName: 'State',
// name: 'state',
// type: 'options',
// options: [
// {
// name: 'Alerting',
// value: 'alerting',
// },
// {
// name: 'Connected',
// value: 'connected',
// },
// {
// name: 'Connecting',
// value: 'connecting',
// },
// {
// name: 'Disconnected',
// value: 'disconnected',
// },
// {
// name: 'Held',
// value: 'held',
// },
// {
// name: 'Remote Held',
// value: 'remoteHeld',
// },
// ],
// displayOptions: {
// show: {
// '/resource': [
// 'telephonyCall',
// ],
// '/event': [
// 'created',
// 'deleted',
// 'updated',
// ],
// },
// },
// default: '',
// description: `Limit to a particular call state`,
// },
] ,
} ,
] ,
} ;
webhookMethods = {
default : {
async checkExists ( this : IHookFunctions ) : Promise < boolean > {
const webhookUrl = this . getNodeWebhookUrl ( 'default' ) ;
const webhookData = this . getWorkflowStaticData ( 'node' ) ;
const resource = this . getNodeParameter ( 'resource' ) as string ;
const event = this . getNodeParameter ( 'event' ) as string ;
// Check all the webhooks which exist already if it is identical to the
// one that is supposed to get created.
const data = await webexApiRequestAllItems . call ( this , 'items' , 'GET' , '/webhooks' ) ;
for ( const webhook of data ) {
2022-08-01 13:47:55 -07:00
if (
webhook . url === webhookUrl &&
webhook . resource === mapResource ( resource ) &&
webhook . event === event &&
webhook . status === 'active'
) {
2021-07-23 13:28:18 -07:00
webhookData . webhookId = webhook . id as string ;
return true ;
}
}
return false ;
} ,
async create ( this : IHookFunctions ) : Promise < boolean > {
const webhookData = this . getWorkflowStaticData ( 'node' ) ;
const webhookUrl = this . getNodeWebhookUrl ( 'default' ) ;
const event = this . getNodeParameter ( 'event' ) as string ;
const resource = this . getNodeParameter ( 'resource' ) as string ;
const filters = this . getNodeParameter ( 'filters' , { } ) as IDataObject ;
2021-08-20 09:57:30 -07:00
const credentials = await this . getCredentials ( 'ciscoWebexOAuth2Api' ) ;
const secret = getAutomaticSecret ( credentials ) ;
2021-07-23 13:28:18 -07:00
const filter = [ ] ;
for ( const key of Object . keys ( filters ) ) {
if ( key !== 'ownedBy' ) {
filter . push ( ` ${ key } = ${ filters [ key ] } ` ) ;
}
}
const endpoint = '/webhooks' ;
const body : IDataObject = {
name : ` n8n-webhook: ${ webhookUrl } ` ,
targetUrl : webhookUrl ,
event ,
resource : mapResource ( resource ) ,
} ;
if ( filters . ownedBy ) {
2022-12-02 12:54:28 -08:00
body . ownedBy = filters . ownedBy as string ;
2021-07-23 13:28:18 -07:00
}
2022-12-02 12:54:28 -08:00
body . secret = secret ;
2021-07-23 13:28:18 -07:00
if ( filter . length ) {
2022-12-02 12:54:28 -08:00
body . filter = filter . join ( '&' ) ;
2021-07-23 13:28:18 -07:00
}
const responseData = await webexApiRequest . call ( this , 'POST' , endpoint , body ) ;
if ( responseData . id === undefined ) {
// Required data is missing so was not successful
return false ;
}
webhookData . webhookId = responseData . id as string ;
webhookData . secret = secret ;
return true ;
} ,
async delete ( this : IHookFunctions ) : Promise < boolean > {
const webhookData = this . getWorkflowStaticData ( 'node' ) ;
if ( webhookData . webhookId !== undefined ) {
const endpoint = ` /webhooks/ ${ webhookData . webhookId } ` ;
try {
await webexApiRequest . call ( this , 'DELETE' , endpoint ) ;
} catch ( error ) {
return false ;
}
// Remove from the static workflow data so that it is clear
2023-03-03 09:49:19 -08:00
// that no webhooks are registered anymore
2021-07-23 13:28:18 -07:00
delete webhookData . webhookId ;
}
return true ;
} ,
} ,
} ;
async webhook ( this : IWebhookFunctions ) : Promise < IWebhookResponseData > {
let bodyData = this . getBodyData ( ) ;
const webhookData = this . getWorkflowStaticData ( 'node' ) ;
const headers = this . getHeaderData ( ) as IDataObject ;
const req = this . getRequestObject ( ) ;
const resolveData = this . getNodeParameter ( 'resolveData' , false ) as boolean ;
2021-08-07 00:46:33 -07:00
2021-07-23 13:28:18 -07:00
//@ts-ignore
2022-08-01 13:47:55 -07:00
const computedSignature = createHmac ( 'sha1' , webhookData . secret )
. update ( req . rawBody )
. digest ( 'hex' ) ;
2021-07-23 13:28:18 -07:00
if ( headers [ 'x-spark-signature' ] !== computedSignature ) {
return { } ;
}
if ( resolveData ) {
2022-08-01 13:47:55 -07:00
const {
data : { id } ,
} = bodyData as { data : { id : string } } ;
2021-07-23 13:28:18 -07:00
bodyData = await webexApiRequest . call ( this , 'GET' , ` /attachment/actions/ ${ id } ` ) ;
}
return {
2022-08-01 13:47:55 -07:00
workflowData : [ this . helpers . returnJsonArray ( bodyData ) ] ,
2021-07-23 13:28:18 -07:00
} ;
}
}