2021-07-23 13:28:18 -07:00
import {
IHookFunctions ,
IWebhookFunctions ,
} from 'n8n-core' ;
import {
IDataObject ,
INodeType ,
INodeTypeDescription ,
IWebhookResponseData ,
2021-08-20 09:57:30 -07:00
NodeOperationError ,
2021-07-23 13:28:18 -07:00
} from 'n8n-workflow' ;
import {
getAutomaticSecret ,
getEvents ,
mapResource ,
webexApiRequest ,
webexApiRequestAllItems ,
} from './GenericFunctions' ;
import {
createHmac ,
} from 'crypto' ;
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 : {
2021-08-07 00:46:33 -07:00
name : 'Webex Trigger' ,
2021-07-23 13:28:18 -07:00
} ,
inputs : [ ] ,
outputs : [ 'main' ] ,
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 : {
resource : [
'attachmentAction' ,
] ,
} ,
} ,
default : true ,
2022-06-20 07:54:01 -07:00
// eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether
2021-10-27 13:00:13 -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 : {
'/resource' : [
'message' ,
] ,
'/event' : [
'created' ,
'deleted' ,
] ,
} ,
} ,
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 : {
'/resource' : [
'room' ,
] ,
'/event' : [
'created' ,
'updated' ,
] ,
} ,
} ,
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 : {
'/resource' : [
'membership' ,
] ,
'/event' : [
'created' ,
'updated' ,
'deleted' ,
] ,
} ,
} ,
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 : {
'/resource' : [
'message' ,
] ,
'/event' : [
'created' ,
'deleted' ,
] ,
} ,
} ,
default : '' ,
2022-05-06 14:01:25 -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 : {
'/resource' : [
'attachmentAction' ,
] ,
'/event' : [
'created' ,
] ,
} ,
} ,
default : '' ,
description : 'Limit to a particular message, by ID' ,
} ,
{
displayName : 'Owned By' ,
name : 'ownedBy' ,
displayOptions : {
show : {
'/resource' : [
'meeting' ,
] ,
} ,
} ,
type : 'string' ,
default : '' ,
} ,
{
displayName : 'Person Email' ,
name : 'personEmail' ,
type : 'string' ,
displayOptions : {
show : {
'/resource' : [
'membership' ,
] ,
'/event' : [
'created' ,
'updated' ,
'deleted' ,
] ,
} ,
} ,
default : '' ,
description : 'Limit to a particular person, by email' ,
} ,
{
displayName : 'Person Email' ,
name : 'personEmail' ,
type : 'string' ,
displayOptions : {
show : {
'/resource' : [
'message' ,
] ,
'/event' : [
'created' ,
'deleted' ,
] ,
} ,
} ,
default : '' ,
description : 'Limit to a particular person, by email' ,
} ,
{
displayName : 'Person ID' ,
name : 'personId' ,
type : 'string' ,
displayOptions : {
show : {
'/resource' : [
'attachmentAction' ,
] ,
'/event' : [
'created' ,
] ,
} ,
} ,
default : '' ,
description : 'Limit to a particular person, by ID' ,
} ,
{
displayName : 'Person ID' ,
name : 'personId' ,
type : 'string' ,
displayOptions : {
show : {
'/resource' : [
'membership' ,
] ,
'/event' : [
'created' ,
'updated' ,
'deleted' ,
] ,
} ,
} ,
default : '' ,
description : 'Limit to a particular person, by ID' ,
} ,
{
displayName : 'Person ID' ,
name : 'personId' ,
type : 'string' ,
displayOptions : {
show : {
'/resource' : [
'message' ,
] ,
'/event' : [
'created' ,
'deleted' ,
] ,
} ,
} ,
default : '' ,
description : 'Limit to a particular person, by ID' ,
} ,
{
displayName : 'Room ID' ,
name : 'roomId' ,
type : 'string' ,
displayOptions : {
show : {
'/resource' : [
'attachmentAction' ,
] ,
'/event' : [
'created' ,
] ,
} ,
} ,
default : '' ,
description : 'Limit to a particular room, by ID' ,
} ,
{
displayName : 'Room ID' ,
name : 'roomId' ,
type : 'string' ,
displayOptions : {
show : {
'/resource' : [
'membership' ,
] ,
'/event' : [
'created' ,
'updated' ,
'deleted' ,
] ,
} ,
} ,
default : '' ,
description : 'Limit to a particular room, by ID' ,
} ,
{
displayName : 'Room ID' ,
name : 'roomId' ,
type : 'string' ,
displayOptions : {
show : {
'/resource' : [
'message' ,
] ,
'/event' : [
'created' ,
'updated' ,
] ,
} ,
} ,
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 : {
'/resource' : [
'message' ,
] ,
'/event' : [
'created' ,
'deleted' ,
] ,
} ,
} ,
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 : {
'/resource' : [
'room' ,
] ,
'/event' : [
'created' ,
'updated' ,
] ,
} ,
} ,
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`,
// },
] ,
} ,
] ,
} ;
// @ts-ignore (because of request)
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 ) {
if ( webhook . url === webhookUrl
&& webhook . resource === mapResource ( resource )
&& webhook . event === event
&& webhook . status === 'active' ) {
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 ) {
body [ 'ownedBy' ] = filters . ownedBy as string ;
}
body [ 'secret' ] = secret ;
if ( filter . length ) {
body [ 'filter' ] = filter . join ( '&' ) ;
}
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
// that no webhooks are registred anymore
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
const computedSignature = createHmac ( 'sha1' , webhookData . secret ) . update ( req . rawBody ) . digest ( 'hex' ) ;
if ( headers [ 'x-spark-signature' ] !== computedSignature ) {
return { } ;
}
if ( resolveData ) {
const { data : { id } } = bodyData as { data : { id : string } } ;
bodyData = await webexApiRequest . call ( this , 'GET' , ` /attachment/actions/ ${ id } ` ) ;
}
return {
workflowData : [
this . helpers . returnJsonArray ( bodyData ) ,
] ,
} ;
}
}