From 83319c30dc14bbee00c9d4ad7c5e98bbd0c0b859 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Fri, 17 Jan 2020 17:48:54 -0500 Subject: [PATCH 1/4] :sparkles: copper trigger --- .../credentials/CopperApi.credentials.ts | 31 ++++ .../nodes/Copper/CopperTrigger.node.ts | 162 ++++++++++++++++++ .../nodes/Copper/GenericFunctions.ts | 44 +++++ packages/nodes-base/nodes/Copper/copper.png | Bin 0 -> 8023 bytes packages/nodes-base/package.json | 2 + 5 files changed, 239 insertions(+) create mode 100644 packages/nodes-base/credentials/CopperApi.credentials.ts create mode 100644 packages/nodes-base/nodes/Copper/CopperTrigger.node.ts create mode 100644 packages/nodes-base/nodes/Copper/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Copper/copper.png diff --git a/packages/nodes-base/credentials/CopperApi.credentials.ts b/packages/nodes-base/credentials/CopperApi.credentials.ts new file mode 100644 index 0000000000..12d2f75096 --- /dev/null +++ b/packages/nodes-base/credentials/CopperApi.credentials.ts @@ -0,0 +1,31 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class CopperApi implements ICredentialType { + name = 'copperApi'; + displayName = 'Copper API'; + properties = [ + { + displayName: 'API Key', + name: 'apiKey', + required: true, + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Email', + name: 'email', + required: true, + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Secret', + name: 'secret', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Copper/CopperTrigger.node.ts b/packages/nodes-base/nodes/Copper/CopperTrigger.node.ts new file mode 100644 index 0000000000..bb16ca6834 --- /dev/null +++ b/packages/nodes-base/nodes/Copper/CopperTrigger.node.ts @@ -0,0 +1,162 @@ +import { + IHookFunctions, + IWebhookFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeTypeDescription, + INodeType, + IWebhookResponseData, +} from 'n8n-workflow'; + +import { + copperApiRequest, +} from './GenericFunctions'; + +export class CopperTrigger implements INodeType { + description: INodeTypeDescription = { + displayName: 'Copper Trigger', + name: 'copper', + icon: 'file:copper.png', + group: ['trigger'], + version: 1, + description: 'Handle Copper events via webhooks', + defaults: { + name: 'Copper Trigger', + color: '#ff2564', + }, + inputs: [], + outputs: ['main'], + credentials: [ + { + name: 'copperApi', + required: true, + } + ], + webhooks: [ + { + name: 'default', + httpMethod: 'POST', + responseMode: 'onReceived', + path: 'webhook', + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + required: true, + default: '', + options: [ + { + name: 'Lead', + value: 'lead', + }, + { + name: 'Person', + value: 'person', + }, + { + name: 'Company', + value: 'company', + }, + { + name: 'Opportunity', + value: 'opportunity', + }, + { + name: 'Project', + value: 'project', + }, + ], + description: 'The resource is gonna fire the event', + }, + { + displayName: 'Event', + name: 'event', + type: 'options', + required: true, + default: '', + options: [ + { + name: 'New', + value: 'new', + description: 'A new record is created', + }, + { + name: 'Update', + value: 'update', + description: 'Any field in the existing entity record is changed', + }, + { + name: 'Delete', + value: 'delete', + description: 'An existing record is removed', + }, + ], + description: 'The resource is gonna fire the event', + }, + ], + }; + // @ts-ignore + webhookMethods = { + default: { + async checkExists(this: IHookFunctions): Promise { + const webhookData = this.getWorkflowStaticData('node'); + if (webhookData.webhookId === undefined) { + return false; + } + const endpoint = `/webhooks/${webhookData.webhookId}`; + try { + await copperApiRequest.call(this, 'GET', endpoint); + } catch (err) { + return false + } + return true; + }, + async create(this: IHookFunctions): Promise { + const webhookUrl = this.getNodeWebhookUrl('default'); + const webhookData = this.getWorkflowStaticData('node'); + const resource = this.getNodeParameter('resource') as string; + const event = this.getNodeParameter('event') as string; + const endpoint = '/webhooks'; + const body: IDataObject = { + target: webhookUrl, + type: resource, + event: event, + }; + const { id } = await copperApiRequest.call(this, 'POST', endpoint, body); + webhookData.webhookId = id; + return true; + }, + async delete(this: IHookFunctions): Promise { + const webhookData = this.getWorkflowStaticData('node'); + const endpoint = `/webhooks/${webhookData.webhookId}`; + try { + await copperApiRequest.call(this, 'DELETE', endpoint); + } catch(error) { + return false; + } + delete webhookData.webhookId; + return true; + }, + }, + }; + + async webhook(this: IWebhookFunctions): Promise { + const credentials = this.getCredentials('copperApi'); + const req = this.getRequestObject(); + if (credentials!.secret) { + if (req.body.secret !== credentials!.secret) { + return {}; + }; + } + return { + workflowData: [ + this.helpers.returnJsonArray(req.body), + ], + }; + } +} diff --git a/packages/nodes-base/nodes/Copper/GenericFunctions.ts b/packages/nodes-base/nodes/Copper/GenericFunctions.ts new file mode 100644 index 0000000000..7c7c5d5967 --- /dev/null +++ b/packages/nodes-base/nodes/Copper/GenericFunctions.ts @@ -0,0 +1,44 @@ +import { OptionsWithUri } from 'request'; +import { + IExecuteFunctions, + IExecuteSingleFunctions, + IHookFunctions, + ILoadOptionsFunctions, + IWebhookFunctions, +} from 'n8n-core'; +import { IDataObject } from 'n8n-workflow'; + +export async function copperApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any + const credentials = this.getCredentials('copperApi'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + if (credentials.secret) { + body.secret = { + secret: credentials.secret as string, + }; + }; + let options: OptionsWithUri = { + headers: { + 'X-PW-AccessToken': credentials.apiKey, + 'X-PW-Application': 'developer_api', + 'X-PW-UserEmail': credentials.email, + 'Content-Type': 'application/json', + }, + method, + qs, + body, + uri: uri ||`https://api.prosperworks.com/developer_api/v1${resource}`, + json: true + }; + options = Object.assign({}, options, option); + if (Object.keys(options.body).length === 0) { + delete options.body; + } + + try { + return await this.helpers.request!(options); + } catch (error) { + throw new Error('Zoom Error: ' + error.message); + } +} diff --git a/packages/nodes-base/nodes/Copper/copper.png b/packages/nodes-base/nodes/Copper/copper.png new file mode 100644 index 0000000000000000000000000000000000000000..befa65c1810d62f9c51fdf304e78c9bf8cc83114 GIT binary patch literal 8023 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 Date: Fri, 17 Jan 2020 17:50:22 -0500 Subject: [PATCH 2/4] :zap: small fix --- packages/nodes-base/nodes/Copper/GenericFunctions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Copper/GenericFunctions.ts b/packages/nodes-base/nodes/Copper/GenericFunctions.ts index 7c7c5d5967..48222181f4 100644 --- a/packages/nodes-base/nodes/Copper/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Copper/GenericFunctions.ts @@ -39,6 +39,6 @@ export async function copperApiRequest(this: IHookFunctions | IExecuteFunctions try { return await this.helpers.request!(options); } catch (error) { - throw new Error('Zoom Error: ' + error.message); + throw new Error('Copper Error: ' + error.message); } } From 6808c51c0abbaabfd3f0f444516f93afb7d116d6 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 17 Jan 2020 22:41:12 -0600 Subject: [PATCH 3/4] :bug: Fix Webflow-Trigger node name --- packages/nodes-base/nodes/Webflow/WebflowTrigger.node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Webflow/WebflowTrigger.node.ts b/packages/nodes-base/nodes/Webflow/WebflowTrigger.node.ts index 53a0a75302..709b9858cd 100644 --- a/packages/nodes-base/nodes/Webflow/WebflowTrigger.node.ts +++ b/packages/nodes-base/nodes/Webflow/WebflowTrigger.node.ts @@ -19,7 +19,7 @@ import { export class WebflowTrigger implements INodeType { description: INodeTypeDescription = { displayName: 'Webflow Trigger', - name: 'webflow', + name: 'webflowTrigger', icon: 'file:webflow.png', group: ['trigger'], version: 1, From 6b45d79d362e6ef848cf74e7b5992fa5d08f9ea3 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 18 Jan 2020 09:28:19 -0600 Subject: [PATCH 4/4] :zap: Minor improvements to CopperTrigger-Node --- .../credentials/CopperApi.credentials.ts | 6 -- .../nodes/Copper/CopperTrigger.node.ts | 56 +++++++++++-------- .../nodes/Copper/GenericFunctions.ts | 33 ++++++++--- 3 files changed, 60 insertions(+), 35 deletions(-) diff --git a/packages/nodes-base/credentials/CopperApi.credentials.ts b/packages/nodes-base/credentials/CopperApi.credentials.ts index 12d2f75096..ea3105054d 100644 --- a/packages/nodes-base/credentials/CopperApi.credentials.ts +++ b/packages/nodes-base/credentials/CopperApi.credentials.ts @@ -21,11 +21,5 @@ export class CopperApi implements ICredentialType { type: 'string' as NodePropertyTypes, default: '', }, - { - displayName: 'Secret', - name: 'secret', - type: 'string' as NodePropertyTypes, - default: '', - }, ]; } diff --git a/packages/nodes-base/nodes/Copper/CopperTrigger.node.ts b/packages/nodes-base/nodes/Copper/CopperTrigger.node.ts index bb16ca6834..2c95c25ebf 100644 --- a/packages/nodes-base/nodes/Copper/CopperTrigger.node.ts +++ b/packages/nodes-base/nodes/Copper/CopperTrigger.node.ts @@ -12,12 +12,13 @@ import { import { copperApiRequest, + getAutomaticSecret, } from './GenericFunctions'; export class CopperTrigger implements INodeType { description: INodeTypeDescription = { displayName: 'Copper Trigger', - name: 'copper', + name: 'copperTrigger', icon: 'file:copper.png', group: ['trigger'], version: 1, @@ -50,28 +51,32 @@ export class CopperTrigger implements INodeType { required: true, default: '', options: [ - { - name: 'Lead', - value: 'lead', - }, - { - name: 'Person', - value: 'person', - }, { name: 'Company', value: 'company', }, + { + name: 'Lead', + value: 'lead', + }, { name: 'Opportunity', value: 'opportunity', }, + { + name: 'Person', + value: 'person', + }, { name: 'Project', value: 'project', }, + { + name: 'Task', + value: 'task', + }, ], - description: 'The resource is gonna fire the event', + description: 'The resource which will fire the event.', }, { displayName: 'Event', @@ -80,6 +85,11 @@ export class CopperTrigger implements INodeType { required: true, default: '', options: [ + { + name: 'Delete', + value: 'delete', + description: 'An existing record is removed', + }, { name: 'New', value: 'new', @@ -90,13 +100,8 @@ export class CopperTrigger implements INodeType { value: 'update', description: 'Any field in the existing entity record is changed', }, - { - name: 'Delete', - value: 'delete', - description: 'An existing record is removed', - }, ], - description: 'The resource is gonna fire the event', + description: 'The event to listen to.', }, ], }; @@ -112,7 +117,7 @@ export class CopperTrigger implements INodeType { try { await copperApiRequest.call(this, 'GET', endpoint); } catch (err) { - return false + return false; } return true; }, @@ -125,8 +130,14 @@ export class CopperTrigger implements INodeType { const body: IDataObject = { target: webhookUrl, type: resource, - event: event, + event, }; + + const credentials = this.getCredentials('copperApi'); + body.secret = { + secret: getAutomaticSecret(credentials!), + }; + const { id } = await copperApiRequest.call(this, 'POST', endpoint, body); webhookData.webhookId = id; return true; @@ -148,11 +159,12 @@ export class CopperTrigger implements INodeType { async webhook(this: IWebhookFunctions): Promise { const credentials = this.getCredentials('copperApi'); const req = this.getRequestObject(); - if (credentials!.secret) { - if (req.body.secret !== credentials!.secret) { - return {}; - }; + + // Check if the supplied secret matches. If not ignore request. + if (req.body.secret !== getAutomaticSecret(credentials!)) { + return {}; } + return { workflowData: [ this.helpers.returnJsonArray(req.body), diff --git a/packages/nodes-base/nodes/Copper/GenericFunctions.ts b/packages/nodes-base/nodes/Copper/GenericFunctions.ts index 48222181f4..31bd1379c2 100644 --- a/packages/nodes-base/nodes/Copper/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Copper/GenericFunctions.ts @@ -1,4 +1,6 @@ +import { createHash } from 'crypto'; import { OptionsWithUri } from 'request'; + import { IExecuteFunctions, IExecuteSingleFunctions, @@ -6,18 +8,17 @@ import { ILoadOptionsFunctions, IWebhookFunctions, } from 'n8n-core'; -import { IDataObject } from 'n8n-workflow'; +import { + ICredentialDataDecryptedObject, + IDataObject, +} from 'n8n-workflow'; export async function copperApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('copperApi'); if (credentials === undefined) { throw new Error('No credentials got returned!'); } - if (credentials.secret) { - body.secret = { - secret: credentials.secret as string, - }; - }; + let options: OptionsWithUri = { headers: { 'X-PW-AccessToken': credentials.apiKey, @@ -39,6 +40,24 @@ export async function copperApiRequest(this: IHookFunctions | IExecuteFunctions try { return await this.helpers.request!(options); } catch (error) { - throw new Error('Copper Error: ' + error.message); + let errorMessage = error.message; + if (error.response.body && error.response.body.message) { + errorMessage = error.response.body.message; + } + + throw new Error('Copper Error: ' + errorMessage); } } + + +/** + * Creates a secret from the credentials + * + * @export + * @param {ICredentialDataDecryptedObject} credentials + * @returns + */ +export function getAutomaticSecret(credentials: ICredentialDataDecryptedObject) { + const data = `${credentials.email},${credentials.apiKey}`; + return createHash('md5').update(data).digest("hex"); +}