diff --git a/packages/nodes-base/credentials/WebflowApi.credentials.ts b/packages/nodes-base/credentials/WebflowApi.credentials.ts new file mode 100644 index 000000000..8ab874b44 --- /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 000000000..030e47f90 --- /dev/null +++ b/packages/nodes-base/nodes/Webflow/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 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) { + + let errorMessage = error.message; + if (error.response.body && error.response.body.err) { + errorMessage = error.response.body.err; + } + + throw new Error('Webflow Error: ' + errorMessage); + } +} 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 000000000..53a0a7530 --- /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: 'Ecomm Inventory Changed', + value: 'ecomm_inventory_changed', + }, + { + name: 'Ecomm New Order', + value: 'ecomm_new_order', + }, + { + name: 'Ecomm Order Changed', + value: 'ecomm_order_changed', + }, + { + name: 'Site Publish', + value: 'site_publish', + }, + ], + 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 000000000..e9b3be66b 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 8e565d787..4afb89307 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -55,14 +55,15 @@ "dist/credentials/MailgunApi.credentials.js", "dist/credentials/MandrillApi.credentials.js", "dist/credentials/MattermostApi.credentials.js", - "dist/credentials/MongoDb.credentials.js", - "dist/credentials/MySql.credentials.js", "dist/credentials/MauticApi.credentials.js", + "dist/credentials/MongoDb.credentials.js", + "dist/credentials/Msg91Api.credentials.js", + "dist/credentials/MySql.credentials.js", "dist/credentials/NextCloudApi.credentials.js", "dist/credentials/OpenWeatherMapApi.credentials.js", + "dist/credentials/PayPalApi.credentials.js", "dist/credentials/PipedriveApi.credentials.js", "dist/credentials/Postgres.credentials.js", - "dist/credentials/PayPalApi.credentials.js", "dist/credentials/Redis.credentials.js", "dist/credentials/RocketchatApi.credentials.js", "dist/credentials/ShopifyApi.credentials.js", @@ -73,13 +74,10 @@ "dist/credentials/TodoistApi.credentials.js", "dist/credentials/TrelloApi.credentials.js", "dist/credentials/TwilioApi.credentials.js", - "dist/credentials/Msg91Api.credentials.js", - "dist/credentials/TypeformApi.credentials.js", - "dist/credentials/MandrillApi.credentials.js", - "dist/credentials/TodoistApi.credentials.js", "dist/credentials/TypeformApi.credentials.js", "dist/credentials/TogglApi.credentials.js", "dist/credentials/VeroApi.credentials.js", + "dist/credentials/WebflowApi.credentials.js", "dist/credentials/WordpressApi.credentials.js", "dist/credentials/ZendeskApi.credentials.js" ], @@ -96,17 +94,17 @@ "dist/nodes/Bitbucket/BitbucketTrigger.node.js", "dist/nodes/Chargebee/Chargebee.node.js", "dist/nodes/Chargebee/ChargebeeTrigger.node.js", - "dist/nodes/Cron.node.js", "dist/nodes/Coda/Coda.node.js", - "dist/nodes/Dropbox/Dropbox.node.js", + "dist/nodes/Cron.node.js", "dist/nodes/Discord/Discord.node.js", + "dist/nodes/Dropbox/Dropbox.node.js", "dist/nodes/EditImage.node.js", "dist/nodes/EmailReadImap.node.js", "dist/nodes/EmailSend.node.js", "dist/nodes/ErrorTrigger.node.js", + "dist/nodes/Eventbrite/EventbriteTrigger.node.js", "dist/nodes/ExecuteCommand.node.js", "dist/nodes/ExecuteWorkflow.node.js", - "dist/nodes/Eventbrite/EventbriteTrigger.node.js", "dist/nodes/FileMaker/FileMaker.node.js", "dist/nodes/Freshdesk/Freshdesk.node.js", "dist/nodes/Flow/Flow.node.js", @@ -125,8 +123,8 @@ "dist/nodes/HttpRequest.node.js", "dist/nodes/Hubspot/Hubspot.node.js", "dist/nodes/If.node.js", - "dist/nodes/Interval.node.js", "dist/nodes/Intercom/Intercom.node.js", + "dist/nodes/Interval.node.js", "dist/nodes/Jira/JiraSoftwareCloud.node.js", "dist/nodes/LinkFish/LinkFish.node.js", "dist/nodes/Mailchimp/Mailchimp.node.js", @@ -134,53 +132,53 @@ "dist/nodes/Mailgun/Mailgun.node.js", "dist/nodes/Mandrill/Mandrill.node.js", "dist/nodes/Mattermost/Mattermost.node.js", - "dist/nodes/Merge.node.js", - "dist/nodes/MoveBinaryData.node.js", - "dist/nodes/MongoDb/MongoDb.node.js", - "dist/nodes/MySql/MySql.node.js", - "dist/nodes/NextCloud/NextCloud.node.js", - "dist/nodes/Mandrill/Mandrill.node.js", "dist/nodes/Mautic/Mautic.node.js", "dist/nodes/Mautic/MauticTrigger.node.js", + "dist/nodes/Merge.node.js", + "dist/nodes/MongoDb/MongoDb.node.js", + "dist/nodes/MoveBinaryData.node.js", + "dist/nodes/Msg91/Msg91.node.js", + "dist/nodes/MySql/MySql.node.js", + "dist/nodes/NextCloud/NextCloud.node.js", "dist/nodes/NoOp.node.js", "dist/nodes/OpenWeatherMap.node.js", + "dist/nodes/PayPal/PayPal.node.js", + "dist/nodes/PayPal/PayPalTrigger.node.js", "dist/nodes/Pipedrive/Pipedrive.node.js", "dist/nodes/Pipedrive/PipedriveTrigger.node.js", "dist/nodes/Postgres/Postgres.node.js", - "dist/nodes/PayPal/PayPal.node.js", - "dist/nodes/PayPal/PayPalTrigger.node.js", - "dist/nodes/Rocketchat/Rocketchat.node.js", "dist/nodes/ReadBinaryFile.node.js", "dist/nodes/ReadBinaryFiles.node.js", - "dist/nodes/Redis/Redis.node.js", "dist/nodes/ReadPdf.node.js", + "dist/nodes/Redis/Redis.node.js", "dist/nodes/RenameKeys.node.js", + "dist/nodes/Rocketchat/Rocketchat.node.js", "dist/nodes/RssFeedRead.node.js", "dist/nodes/Set.node.js", - "dist/nodes/SseTrigger.node.js", - "dist/nodes/SplitInBatches.node.js", + "dist/nodes/Shopify/ShopifyTrigger.node.js", "dist/nodes/Slack/Slack.node.js", + "dist/nodes/SplitInBatches.node.js", "dist/nodes/SpreadsheetFile.node.js", + "dist/nodes/SseTrigger.node.js", "dist/nodes/Start.node.js", "dist/nodes/Stripe/StripeTrigger.node.js", - "dist/nodes/Shopify/ShopifyTrigger.node.js", "dist/nodes/Switch.node.js", "dist/nodes/Telegram/Telegram.node.js", "dist/nodes/Telegram/TelegramTrigger.node.js", "dist/nodes/Todoist/Todoist.node.js", + "dist/nodes/Toggl/TogglTrigger.node.js", "dist/nodes/Trello/Trello.node.js", "dist/nodes/Trello/TrelloTrigger.node.js", "dist/nodes/Twilio/Twilio.node.js", - "dist/nodes/Msg91/Msg91.node.js", "dist/nodes/Typeform/TypeformTrigger.node.js", - "dist/nodes/Toggl/TogglTrigger.node.js", "dist/nodes/Vero/Vero.node.js", - "dist/nodes/WriteBinaryFile.node.js", + "dist/nodes/Webflow/WebflowTrigger.node.js", "dist/nodes/Webhook.node.js", "dist/nodes/Wordpress/Wordpress.node.js", + "dist/nodes/WriteBinaryFile.node.js", "dist/nodes/Xml.node.js", - "dist/nodes/Zendesk/ZendeskTrigger.node.js", - "dist/nodes/Zendesk/Zendesk.node.js" + "dist/nodes/Zendesk/Zendesk.node.js", + "dist/nodes/Zendesk/ZendeskTrigger.node.js" ] }, "devDependencies": {