diff --git a/packages/nodes-base/credentials/WebflowApi.credentials.ts b/packages/nodes-base/credentials/WebflowApi.credentials.ts new file mode 100644 index 0000000000..8ab874b449 --- /dev/null +++ b/packages/nodes-base/credentials/WebflowApi.credentials.ts @@ -0,0 +1,17 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class WebflowApi implements ICredentialType { + name = 'webflowApi'; + displayName = 'Webflow API'; + properties = [ + { + displayName: 'Access Token', + name: 'accessToken', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Webflow/GenericFunctions.ts b/packages/nodes-base/nodes/Webflow/GenericFunctions.ts new file mode 100644 index 0000000000..7531cd0334 --- /dev/null +++ b/packages/nodes-base/nodes/Webflow/GenericFunctions.ts @@ -0,0 +1,38 @@ +import { OptionsWithUri } from 'request'; +import { + IExecuteFunctions, + IExecuteSingleFunctions, + IHookFunctions, + ILoadOptionsFunctions, + IWebhookFunctions, +} from 'n8n-core'; +import { IDataObject } from 'n8n-workflow'; + +export async function webflowApiRequest(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('webflowApi'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + let options: OptionsWithUri = { + headers: { + authorization: `Bearer ${credentials.accessToken}`, + 'accept-version': '1.0.0', + }, + method, + qs, + body, + uri: uri ||`https://api.webflow.com${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('Webflow Error: ' + error.message); + } +} diff --git a/packages/nodes-base/nodes/Webflow/WebflowTrigger.node.ts b/packages/nodes-base/nodes/Webflow/WebflowTrigger.node.ts new file mode 100644 index 0000000000..9952c256a7 --- /dev/null +++ b/packages/nodes-base/nodes/Webflow/WebflowTrigger.node.ts @@ -0,0 +1,171 @@ +import { + IHookFunctions, + IWebhookFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeTypeDescription, + INodeType, + IWebhookResponseData, + ILoadOptionsFunctions, + INodePropertyOptions, +} from 'n8n-workflow'; + +import { + webflowApiRequest, +} from './GenericFunctions'; + +export class WebflowTrigger implements INodeType { + description: INodeTypeDescription = { + displayName: 'Webflow Trigger', + name: 'webflow', + icon: 'file:webflow.png', + group: ['trigger'], + version: 1, + description: 'Handle Webflow events via webhooks', + defaults: { + name: 'Webflow Trigger', + color: '#245bf8', + }, + inputs: [], + outputs: ['main'], + credentials: [ + { + name: 'webflowApi', + required: true, + } + ], + webhooks: [ + { + name: 'default', + httpMethod: 'POST', + responseMode: 'onReceived', + path: 'webhook', + }, + ], + properties: [ + { + displayName: 'Site', + name: 'site', + type: 'options', + required: true, + default: '', + typeOptions: { + loadOptionsMethod: 'getSites', + }, + description: 'Site that will trigger the events', + }, + { + displayName: 'Event', + name: 'event', + type: 'options', + required: true, + options: [ + { + name: 'Form submission', + value: 'form_submission', + }, + { + name: 'Site Publish', + value: 'site_publish', + }, + { + name: 'Ecomm New Order', + value: 'ecomm_new_order', + }, + { + name: 'Ecomm Order Changed', + value: 'ecomm_order_changed', + }, + { + name: 'Ecomm Inventory Changed', + value: 'ecomm_inventory_changed', + }, + ], + default: 'form_submission', + }, + ], + }; + + methods = { + loadOptions: { + // Get all the sites to display them to user so that he can + // select them easily + async getSites(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const sites = await webflowApiRequest.call(this, 'GET', '/sites'); + for (const site of sites) { + const siteName = site.name; + const siteId = site._id; + returnData.push({ + name: siteName, + value: siteId, + }); + } + return returnData; + }, + }, + } + + // @ts-ignore + webhookMethods = { + default: { + async checkExists(this: IHookFunctions): Promise { + const webhookData = this.getWorkflowStaticData('node'); + const siteId = this.getNodeParameter('site') as string; + if (webhookData.webhookId === undefined) { + return false; + } + const endpoint = `/sites/${siteId}/webhooks/${webhookData.webhookId}`; + try { + await webflowApiRequest.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 siteId = this.getNodeParameter('site') as string; + const event = this.getNodeParameter('event') as string; + const endpoint = `/sites/${siteId}/webhooks`; + const body: IDataObject = { + site_id: siteId, + triggerType: event, + url: webhookUrl, + + }; + const { _id } = await webflowApiRequest.call(this, 'POST', endpoint, body); + webhookData.webhookId = _id; + return true; + }, + async delete(this: IHookFunctions): Promise { + let responseData; + const webhookData = this.getWorkflowStaticData('node'); + const siteId = this.getNodeParameter('site') as string; + const endpoint = `/sites/${siteId}/webhooks/${webhookData.webhookId}`; + try { + responseData = await webflowApiRequest.call(this, 'DELETE', endpoint); + } catch(error) { + return false; + } + if (!responseData.deleted) { + return false; + } + delete webhookData.webhookId; + return true; + }, + }, + }; + + async webhook(this: IWebhookFunctions): Promise { + const req = this.getRequestObject(); + return { + workflowData: [ + this.helpers.returnJsonArray(req.body), + ], + }; + } +} diff --git a/packages/nodes-base/nodes/Webflow/webflow.png b/packages/nodes-base/nodes/Webflow/webflow.png new file mode 100644 index 0000000000..e9b3be66b3 Binary files /dev/null and b/packages/nodes-base/nodes/Webflow/webflow.png differ diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 8e565d7870..936492b043 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -81,6 +81,7 @@ "dist/credentials/TogglApi.credentials.js", "dist/credentials/VeroApi.credentials.js", "dist/credentials/WordpressApi.credentials.js", + "dist/credentials/WebflowApi.credentials.js", "dist/credentials/ZendeskApi.credentials.js" ], "nodes": [ @@ -178,6 +179,7 @@ "dist/nodes/WriteBinaryFile.node.js", "dist/nodes/Webhook.node.js", "dist/nodes/Wordpress/Wordpress.node.js", + "dist/nodes/Webflow/WebflowTrigger.node.js", "dist/nodes/Xml.node.js", "dist/nodes/Zendesk/ZendeskTrigger.node.js", "dist/nodes/Zendesk/Zendesk.node.js"