diff --git a/packages/nodes-base/credentials/JotFormApi.credentials.ts b/packages/nodes-base/credentials/JotFormApi.credentials.ts new file mode 100644 index 000000000..73d4cd267 --- /dev/null +++ b/packages/nodes-base/credentials/JotFormApi.credentials.ts @@ -0,0 +1,17 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class JotFormApi implements ICredentialType { + name = 'jotFormApi'; + displayName = 'JotForm API'; + properties = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/JotForm/GenericFunctions.ts b/packages/nodes-base/nodes/JotForm/GenericFunctions.ts new file mode 100644 index 000000000..343ca0acb --- /dev/null +++ b/packages/nodes-base/nodes/JotForm/GenericFunctions.ts @@ -0,0 +1,37 @@ +import { OptionsWithUri } from 'request'; +import { + IExecuteFunctions, + IExecuteSingleFunctions, + IHookFunctions, + ILoadOptionsFunctions, + IWebhookFunctions, +} from 'n8n-core'; +import { IDataObject } from 'n8n-workflow'; + +export async function jotformApiRequest(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('jotFormApi'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + let options: OptionsWithUri = { + headers: { + 'APIKEY': credentials.apiKey, + 'Content-Type': 'application/x-www-form-urlencoded', + }, + method, + qs, + form: body, + uri: uri ||`http://api.jotform.com${resource}`, + json: true + }; + if (!Object.keys(body).length) { + delete options.form; + } + options = Object.assign({}, options, option); + + try { + return await this.helpers.request!(options); + } catch (error) { + throw new Error('JotForm Error: ' + error); + } +} diff --git a/packages/nodes-base/nodes/JotForm/JotFormTrigger.node.ts b/packages/nodes-base/nodes/JotForm/JotFormTrigger.node.ts new file mode 100644 index 000000000..cb86720df --- /dev/null +++ b/packages/nodes-base/nodes/JotForm/JotFormTrigger.node.ts @@ -0,0 +1,150 @@ +import { + IHookFunctions, + IWebhookFunctions, +} from 'n8n-core'; + +import { + IDataObject, + ILoadOptionsFunctions, + INodePropertyOptions, + INodeTypeDescription, + INodeType, + IWebhookResponseData, +} from 'n8n-workflow'; + +import { + jotformApiRequest, +} from './GenericFunctions'; + +export class JotFormTrigger implements INodeType { + description: INodeTypeDescription = { + displayName: 'JotForm Trigger', + name: 'jotFormTrigger', + icon: 'file:jotform.png', + group: ['trigger'], + version: 1, + description: 'Handle JotForm events via webhooks', + defaults: { + name: 'JotForm Trigger', + color: '#fa8900', + }, + inputs: [], + outputs: ['main'], + credentials: [ + { + name: 'jotFormApi', + required: true, + } + ], + webhooks: [ + { + name: 'default', + httpMethod: 'POST', + responseMode: 'onReceived', + path: 'webhook', + }, + ], + properties: [ + { + displayName: 'Form', + name: 'form', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getForms' + }, + default: '', + description: '', + }, + ], + + }; + + methods = { + loadOptions: { + // Get all the available forms to display them to user so that he can + // select them easily + async getForms(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const qs: IDataObject = { + limit: 1000, + }; + const forms = await jotformApiRequest.call(this, 'GET', '/user/forms', {}, qs); + for (const form of forms.content) { + const formName = form.title; + const formId = form.id; + returnData.push({ + name: formName, + value: formId, + }); + } + return returnData; + }, + }, + }; + // @ts-ignore + webhookMethods = { + default: { + async checkExists(this: IHookFunctions): Promise { + const webhookData = this.getWorkflowStaticData('node'); + const formId = this.getNodeParameter('form') as string; + const endpoint = `/form/${formId}/webhooks`; + + try { + const responseData = await jotformApiRequest.call(this, 'GET', endpoint); + + const webhookUrls = Object.values(responseData.content); + const webhookUrl = this.getNodeWebhookUrl('default'); + if (!webhookUrls.includes(webhookUrl)) { + return false; + } + + const webhookIds = Object.keys(responseData.content); + webhookData.webhookId = webhookIds[webhookUrls.indexOf(webhookUrl)]; + } catch (e) { + return false; + } + return true; + }, + async create(this: IHookFunctions): Promise { + const webhookUrl = this.getNodeWebhookUrl('default'); + const webhookData = this.getWorkflowStaticData('node'); + const formId = this.getNodeParameter('form') as string; + const endpoint = `/form/${formId}/webhooks`; + const body: IDataObject = { + webhookURL: webhookUrl, + //webhookURL: 'https://en0xsizp3qyt7f.x.pipedream.net/', + }; + const { content } = await jotformApiRequest.call(this, 'POST', endpoint, body); + webhookData.webhookId = Object.keys(content)[0]; + return true; + }, + async delete(this: IHookFunctions): Promise { + let responseData; + const webhookData = this.getWorkflowStaticData('node'); + const formId = this.getNodeParameter('form') as string; + const endpoint = `/form/${formId}/webhooks/${webhookData.webhookId}`; + try { + responseData = await jotformApiRequest.call(this, 'DELETE', endpoint); + } catch(error) { + return false; + } + if (responseData.message !== 'success') { + return false; + } + delete webhookData.webhookId; + return true; + }, + }, + }; + + //@ts-ignore + async webhook(this: IWebhookFunctions): Promise { + const req = this.getRequestObject(); + return { + workflowData: [ + this.helpers.returnJsonArray(req.body) + ], + }; + } +} diff --git a/packages/nodes-base/nodes/JotForm/jotform.png b/packages/nodes-base/nodes/JotForm/jotform.png new file mode 100644 index 000000000..229b0d362 Binary files /dev/null and b/packages/nodes-base/nodes/JotForm/jotform.png differ diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 1ca255cc0..eb19ffcb5 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -56,6 +56,7 @@ "dist/credentials/Imap.credentials.js", "dist/credentials/IntercomApi.credentials.js", "dist/credentials/JiraSoftwareCloudApi.credentials.js", + "dist/credentials/JotFormApi.credentials.js", "dist/credentials/LinkFishApi.credentials.js", "dist/credentials/MailchimpApi.credentials.js", "dist/credentials/MailgunApi.credentials.js", @@ -141,6 +142,7 @@ "dist/nodes/Intercom/Intercom.node.js", "dist/nodes/Interval.node.js", "dist/nodes/Jira/JiraSoftwareCloud.node.js", + "dist/nodes/JotForm/JotFormTrigger.node.js", "dist/nodes/LinkFish/LinkFish.node.js", "dist/nodes/Mailchimp/Mailchimp.node.js", "dist/nodes/Mailchimp/MailchimpTrigger.node.js",