mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-15 17:14:05 -08:00
7ce7285f7a
* Changes to types so that credentials can be always loaded from DB This first commit changes all return types from the execute functions and calls to get credentials to be async so we can use await. This is a first step as previously credentials were loaded in memory and always available. We will now be loading them from the DB which requires turning the whole call chain async. * Fix updated files * Removed unnecessary credential loading to improve performance * Fix typo * ⚡ Fix issue * Updated new nodes to load credentials async * ⚡ Remove not needed comment Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
689 lines
15 KiB
TypeScript
689 lines
15 KiB
TypeScript
import {
|
|
IHookFunctions,
|
|
IWebhookFunctions,
|
|
} from 'n8n-core';
|
|
|
|
import {
|
|
IDataObject,
|
|
INodeType,
|
|
INodeTypeDescription,
|
|
IWebhookResponseData,
|
|
NodeOperationError,
|
|
} from 'n8n-workflow';
|
|
|
|
import {
|
|
getAutomaticSecret,
|
|
getEvents,
|
|
mapResource,
|
|
webexApiRequest,
|
|
webexApiRequestAllItems,
|
|
} from './GenericFunctions';
|
|
|
|
import {
|
|
createHmac,
|
|
} from 'crypto';
|
|
|
|
export class CiscoWebexTrigger implements INodeType {
|
|
description: INodeTypeDescription = {
|
|
displayName: 'Webex by Cisco Trigger',
|
|
name: 'ciscoWebexTrigger',
|
|
icon: 'file:ciscoWebex.png',
|
|
group: ['trigger'],
|
|
version: 1,
|
|
subtitle: '={{$parameter["resource"] + ":" + $parameter["event"]}}',
|
|
description: 'Starts the workflow when Cisco Webex events occur.',
|
|
defaults: {
|
|
name: 'Webex Trigger',
|
|
color: '#29b6f6',
|
|
},
|
|
inputs: [],
|
|
outputs: ['main'],
|
|
credentials: [
|
|
{
|
|
name: 'ciscoWebexOAuth2Api',
|
|
required: true,
|
|
},
|
|
],
|
|
webhooks: [
|
|
{
|
|
name: 'default',
|
|
httpMethod: 'POST',
|
|
responseMode: 'onReceived',
|
|
path: 'webhook',
|
|
},
|
|
],
|
|
properties: [
|
|
{
|
|
displayName: 'Resource',
|
|
name: 'resource',
|
|
type: 'options',
|
|
options: [
|
|
{
|
|
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',
|
|
},
|
|
{
|
|
name: '*',
|
|
value: 'all',
|
|
},
|
|
],
|
|
default: 'meeting',
|
|
required: true,
|
|
},
|
|
...getEvents(),
|
|
{
|
|
displayName: 'Resolve Data',
|
|
name: 'resolveData',
|
|
type: 'boolean',
|
|
displayOptions: {
|
|
show: {
|
|
resource: [
|
|
'attachmentAction',
|
|
],
|
|
},
|
|
},
|
|
default: true,
|
|
description: 'By default the response only contain a reference to the data the user inputed<br />If this option gets activated it will resolve the data automatically.',
|
|
},
|
|
{
|
|
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,
|
|
description: 'Limit to messages which contain file content attachments',
|
|
},
|
|
{
|
|
displayName: 'Is Locked',
|
|
name: 'isLocked',
|
|
type: 'boolean',
|
|
displayOptions: {
|
|
show: {
|
|
'/resource': [
|
|
'room',
|
|
],
|
|
'/event': [
|
|
'created',
|
|
'updated',
|
|
],
|
|
},
|
|
},
|
|
default: false,
|
|
description: 'Limit to rooms that are locked',
|
|
},
|
|
{
|
|
displayName: 'Is Moderator',
|
|
name: 'isModerator',
|
|
type: 'boolean',
|
|
displayOptions: {
|
|
show: {
|
|
'/resource': [
|
|
'membership',
|
|
],
|
|
'/event': [
|
|
'created',
|
|
'updated',
|
|
'deleted',
|
|
],
|
|
},
|
|
},
|
|
default: false,
|
|
description: 'Limit to moderators of a room',
|
|
},
|
|
{
|
|
displayName: 'Mentioned People',
|
|
name: 'mentionedPeople',
|
|
type: 'string',
|
|
displayOptions: {
|
|
show: {
|
|
'/resource': [
|
|
'message',
|
|
],
|
|
'/event': [
|
|
'created',
|
|
'deleted',
|
|
],
|
|
},
|
|
},
|
|
default: '',
|
|
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`,
|
|
},
|
|
{
|
|
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: '',
|
|
description: `Limit to a particular room type`,
|
|
},
|
|
{
|
|
displayName: 'Type',
|
|
name: 'type',
|
|
type: 'options',
|
|
options: [
|
|
{
|
|
name: 'Direct',
|
|
value: 'direct',
|
|
},
|
|
{
|
|
name: 'Group',
|
|
value: 'group',
|
|
},
|
|
],
|
|
displayOptions: {
|
|
show: {
|
|
'/resource': [
|
|
'room',
|
|
],
|
|
'/event': [
|
|
'created',
|
|
'updated',
|
|
],
|
|
},
|
|
},
|
|
default: '',
|
|
description: `Limit to a particular room type`,
|
|
},
|
|
// {
|
|
// 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;
|
|
const credentials = await this.getCredentials('ciscoWebexOAuth2Api');
|
|
if (credentials === undefined) {
|
|
throw new NodeOperationError(this.getNode(), 'Credentials could not be obtained');
|
|
}
|
|
const secret = getAutomaticSecret(credentials);
|
|
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;
|
|
|
|
//@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),
|
|
],
|
|
};
|
|
}
|
|
}
|