diff --git a/packages/nodes-base/credentials/VonageApi.credentials.ts b/packages/nodes-base/credentials/VonageApi.credentials.ts new file mode 100644 index 0000000000..67fe4bae55 --- /dev/null +++ b/packages/nodes-base/credentials/VonageApi.credentials.ts @@ -0,0 +1,23 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class VonageApi implements ICredentialType { + name = 'vonageApi'; + displayName = 'Vonage API'; + properties = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'API Secret', + name: 'apiSecret', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Vonage/GenericFunctions.ts b/packages/nodes-base/nodes/Vonage/GenericFunctions.ts new file mode 100644 index 0000000000..465620ccc7 --- /dev/null +++ b/packages/nodes-base/nodes/Vonage/GenericFunctions.ts @@ -0,0 +1,50 @@ +import { + OptionsWithUri, +} from 'request'; + +import { + IExecuteFunctions, + IExecuteSingleFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; + +import { + IDataObject, +} from 'n8n-workflow'; + +export async function vonageApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, path: string, body: any = {}, qs: IDataObject = {}, option = {}): Promise { // tslint:disable-line:no-any + + const credentials = this.getCredentials('vonageApi') as IDataObject; + + body.api_key = credentials.apiKey as string; + + body.api_secret = credentials.apiSecret as string; + + const options: OptionsWithUri = { + method, + form: body, + qs, + uri: `https://rest.nexmo.com${path}`, + json: true, + }; + + try { + if (Object.keys(body).length === 0) { + delete options.body; + } + //@ts-ignore + return await this.helpers.request.call(this, options); + } catch (error) { + if (error.response && error.response.body && error.response.body.error) { + + let errors = error.response.body.error.errors; + + errors = errors.map((e: IDataObject) => e.message); + // Try to return the error prettier + throw new Error( + `Vonage error response [${error.statusCode}]: ${errors.join('|')}` + ); + } + throw error; + } +} diff --git a/packages/nodes-base/nodes/Vonage/Vonage.node.ts b/packages/nodes-base/nodes/Vonage/Vonage.node.ts new file mode 100644 index 0000000000..500b14d078 --- /dev/null +++ b/packages/nodes-base/nodes/Vonage/Vonage.node.ts @@ -0,0 +1,510 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import { + vonageApiRequest, +} from './GenericFunctions'; + +export class Vonage implements INodeType { + description: INodeTypeDescription = { + displayName: 'Vonage', + name: 'vonage', + icon: 'file:vonage.png', + group: ['input'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Vonage API', + defaults: { + name: 'Vonage', + color: '#000000', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'vonageApi', + required: true, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'SMS', + value: 'sms', + }, + ], + default: 'sms', + description: 'The resource to operate on.' + }, + { + displayName: 'Operation', + name: 'operation', + type: 'options', + options: [ + { + name: 'Send', + value: 'send', + }, + ], + displayOptions: { + show: { + resource: [ + 'sms', + ], + }, + }, + default: 'send', + description: 'The resource to operate on.' + }, + { + displayName: 'From', + name: 'from', + type: 'string', + displayOptions: { + show: { + resource: [ + 'sms', + ], + operation: [ + 'send', + ], + }, + }, + default: '', + description: `The name or number the message should be sent from`, + }, + { + displayName: 'To', + name: 'to', + type: 'string', + displayOptions: { + show: { + resource: [ + 'sms', + ], + operation: [ + 'send', + ], + }, + }, + default: '', + description: `The number that the message should be sent to. Numbers are specified in E.164 format.`, + }, + { + displayName: 'Type', + name: 'type', + type: 'options', + displayOptions: { + show: { + resource: [ + 'sms', + ], + operation: [ + 'send', + ], + }, + }, + options: [ + { + name: 'Binary', + value: 'binary', + }, + { + name: 'Text', + value: 'text', + }, + { + name: 'Wappush', + value: 'wappush', + }, + { + name: 'Unicode', + value: 'unicode', + }, + { + name: 'VCAL', + value: 'vcal', + }, + { + name: 'VCARD', + value: 'vcard', + }, + ], + default: 'text', + description: 'The format of the message body', + }, + // { + // displayName: 'Binary Property', + // name: 'binaryPropertyName', + // displayOptions: { + // show: { + // resource: [ + // 'sms', + // ], + // operation: [ + // 'send', + // ], + // type: [ + // 'binary', + // ], + // }, + // }, + // type: 'string', + // default: 'data', + // description: 'Object property name which holds binary data.', + // required: true, + // }, + { + displayName: 'Body', + name: 'body', + type: 'string', + displayOptions: { + show: { + resource: [ + 'sms', + ], + operation: [ + 'send', + ], + type: [ + 'binary', + ], + }, + }, + default: '', + description: 'Hex encoded binary data', + }, + { + displayName: 'UDH', + name: 'udh', + type: 'string', + displayOptions: { + show: { + resource: [ + 'sms', + ], + operation: [ + 'send', + ], + type: [ + 'binary', + ], + }, + }, + default: '', + description: 'Your custom Hex encoded User Data Header', + }, + { + displayName: 'Title', + name: 'title', + displayOptions: { + show: { + resource: [ + 'sms', + ], + operation: [ + 'send', + ], + type: [ + 'wappush', + ], + }, + }, + type: 'string', + default: '', + description: 'The title for a wappush SMS', + }, + { + displayName: 'URL', + name: 'url', + type: 'string', + displayOptions: { + show: { + resource: [ + 'sms', + ], + operation: [ + 'send', + ], + type: [ + 'wappush', + ], + }, + }, + default: '', + description: 'The URL of your website', + }, + { + displayName: 'Validity (in minutes)', + name: 'validity', + type: 'number', + default: 0, + displayOptions: { + show: { + resource: [ + 'sms', + ], + operation: [ + 'send', + ], + type: [ + 'wappush', + ], + }, + }, + description: 'The availability for an SMS in minutes', + }, + { + displayName: 'Message', + name: 'message', + type: 'string', + displayOptions: { + show: { + resource: [ + 'sms', + ], + operation: [ + 'send', + ], + type: [ + 'text', + 'unicode', + ], + }, + }, + default: '', + description: `The body of the message being sent`, + }, + { + displayName: 'VCard', + name: 'vcard', + type: 'string', + displayOptions: { + show: { + resource: [ + 'sms', + ], + operation: [ + 'send', + ], + type: [ + 'vcard', + ], + }, + }, + default: '', + description: 'A business card in vCard format', + }, + { + displayName: 'VCal', + name: 'vcal', + type: 'string', + displayOptions: { + show: { + resource: [ + 'sms', + ], + operation: [ + 'send', + ], + type: [ + 'vcal', + ], + }, + }, + default: '', + description: 'A calendar event in vCal format', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + resource: [ + 'sms', + ], + operation: [ + 'send', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Account Ref', + name: 'account-ref', + type: 'string', + default: '', + description: 'An optional string used to identify separate accounts using the SMS endpoint for billing purposes. To use this feature, please email support@nexmo.com', + }, + { + displayName: 'Callback', + name: 'callback', + type: 'string', + default: '', + description: 'The webhook endpoint the delivery receipt for this sms is sent to. This parameter overrides the webhook endpoint you set in Dashboard.', + }, + { + displayName: 'Client Ref', + name: 'client-ref', + type: 'string', + default: '', + description: 'You can optionally include your own reference of up to 40 characters.', + }, + { + displayName: 'Message Class', + name: 'message-class', + type: 'options', + options: [ + { + name: '0', + value: 0, + }, + { + name: '1', + value: 1, + }, + { + name: '2', + value: 2, + }, + { + name: '3', + value: 3, + }, + ], + default: '', + description: 'The Data Coding Scheme value of the message', + }, + { + displayName: 'Protocol ID', + name: 'protocol-id', + type: 'string', + default: '', + description: 'The value of the protocol identifier to use. Ensure that the value is aligned with udh.', + }, + { + displayName: 'Status Report Req', + name: 'status-report-req', + type: 'boolean', + default: false, + description: 'Boolean indicating if you like to receive a Delivery Receipt.', + }, + { + displayName: 'TTL (in minutes)', + name: 'ttl', + type: 'number', + default: 4320, + description: 'By default Nexmo attempt delivery for 72 hours', + }, + ], + }, + ], + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = (items.length as unknown) as number; + const qs: IDataObject = {}; + let responseData; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + for (let i = 0; i < length; i++) { + + if (resource === 'sms') { + + if (operation === 'send') { + + const from = this.getNodeParameter('from', i) as string; + + const to = this.getNodeParameter('to', i) as string; + + const type = this.getNodeParameter('type', i) as string; + + const body: IDataObject = { + from, + to, + type, + }; + + if (type === 'text' || type === 'unicode') { + const message = this.getNodeParameter('message', i) as string; + + body.text = message; + } + + if (type === 'binary') { + const data = this.getNodeParameter('body', i) as string; + + const udh = this.getNodeParameter('udh', i) as string; + + body.udh = udh; + + body.body = data; + + } + + if (type === 'wappush') { + const title = this.getNodeParameter('title', i) as string; + + const url = this.getNodeParameter('url', i) as string; + + const validity = this.getNodeParameter('validity', i) as number; + + body.title = title; + + body.url = url; + + body.validity = validity * 60000; + } + + if (type === 'vcard') { + const vcard = this.getNodeParameter('vcard', i) as string; + + body.vcard = vcard; + } + + if (type === 'vcal') { + const vcal = this.getNodeParameter('vcal', i) as string; + + body.vcal = vcal; + } + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + Object.assign(body, additionalFields); + + if (body.ttl) { + // transform minutes to milliseconds + body.ttl = (body.ttl as number) * 60000; + } + + responseData = await vonageApiRequest.call(this, 'POST', '/sms/json', body); + + responseData = responseData.messages; + } + } + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else if (responseData !== undefined) { + returnData.push(responseData as IDataObject); + } + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Vonage/vonage.png b/packages/nodes-base/nodes/Vonage/vonage.png new file mode 100644 index 0000000000..3da692277e Binary files /dev/null and b/packages/nodes-base/nodes/Vonage/vonage.png differ diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index beddd2e23d..51169cc1bb 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -178,6 +178,7 @@ "dist/credentials/UnleashedSoftwareApi.credentials.js", "dist/credentials/UpleadApi.credentials.js", "dist/credentials/VeroApi.credentials.js", + "dist/credentials/VonageApi.credentials.js", "dist/credentials/WebflowApi.credentials.js", "dist/credentials/WebflowOAuth2Api.credentials.js", "dist/credentials/WooCommerceApi.credentials.js", @@ -370,6 +371,7 @@ "dist/nodes/UnleashedSoftware/UnleashedSoftware.node.js", "dist/nodes/Uplead/Uplead.node.js", "dist/nodes/Vero/Vero.node.js", + "dist/nodes/Vonage/Vonage.node.js", "dist/nodes/Webflow/WebflowTrigger.node.js", "dist/nodes/Webhook.node.js", "dist/nodes/Wordpress/Wordpress.node.js",