From 214b2d19ac55b5e919915ce35ad748ff6e7e1966 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Sun, 11 Oct 2020 12:34:05 -0400 Subject: [PATCH] :zap: Improvements on AWS SES Node (#1029) * :zap: Improvements * :zap: Remove unnecessary change --- packages/nodes-base/nodes/Aws/AwsSes.node.ts | 282 ------ .../nodes-base/nodes/Aws/GenericFunctions.ts | 2 + .../nodes-base/nodes/Aws/SES/AwsSes.node.ts | 848 ++++++++++++++++++ .../nodes/Aws/SES/GenericFunctions.ts | 116 +++ .../nodes-base/nodes/Aws/{ => SES}/ses.png | Bin packages/nodes-base/package.json | 2 +- 6 files changed, 967 insertions(+), 283 deletions(-) delete mode 100644 packages/nodes-base/nodes/Aws/AwsSes.node.ts create mode 100644 packages/nodes-base/nodes/Aws/SES/AwsSes.node.ts create mode 100644 packages/nodes-base/nodes/Aws/SES/GenericFunctions.ts rename packages/nodes-base/nodes/Aws/{ => SES}/ses.png (100%) diff --git a/packages/nodes-base/nodes/Aws/AwsSes.node.ts b/packages/nodes-base/nodes/Aws/AwsSes.node.ts deleted file mode 100644 index 4e87a2dd3e..0000000000 --- a/packages/nodes-base/nodes/Aws/AwsSes.node.ts +++ /dev/null @@ -1,282 +0,0 @@ -import { IExecuteFunctions } from 'n8n-core'; -import { - IDataObject, - INodeExecutionData, - INodeType, - INodeTypeDescription, -} from 'n8n-workflow'; - -import { awsApiRequestSOAP } from './GenericFunctions'; - -function setParameter(params: string[], base: string, values: string[]) { - for (let i = 0; i < values.length; i++) { - params.push(`${base}.${i+1}=${values[i]}`); - } -} - -export class AwsSes implements INodeType { - description: INodeTypeDescription = { - displayName: 'AWS SES', - name: 'awsSes', - icon: 'file:ses.png', - group: ['output'], - version: 1, - subtitle: '={{$parameter["operation"]}}', - description: 'Sends data to AWS SES', - defaults: { - name: 'AWS SES', - color: '#FF9900', - }, - inputs: ['main'], - outputs: ['main'], - credentials: [ - { - name: 'aws', - required: true, - } - ], - properties: [ - { - displayName: 'Operation', - name: 'operation', - type: 'options', - options: [ - { - name: 'Send Email', - value: 'sendEmail', - }, - ], - default: 'sendEmail', - description: 'The operation to perform.', - }, - { - displayName: 'Is Body HTML', - name: 'isBodyHtml', - type: 'boolean', - displayOptions: { - show: { - operation: [ - 'sendEmail', - ], - }, - }, - default: false, - description: 'If body is HTML or simple text.', - }, - { - displayName: 'Subject', - name: 'subject', - type: 'string', - displayOptions: { - show: { - operation: [ - 'sendEmail', - ], - }, - }, - default: '', - required: true, - }, - { - displayName: 'Body', - name: 'body', - type: 'string', - typeOptions: { - alwaysOpenEditWindow: true, - }, - displayOptions: { - show: { - operation: [ - 'sendEmail', - ], - }, - }, - default: '', - description: 'The message to be sent.', - required: true, - }, - { - displayName: 'From Email', - name: 'fromEmail', - type: 'string', - displayOptions: { - show: { - operation: [ - 'sendEmail', - ], - }, - }, - required: true, - description: 'Email address of the sender.', - placeholder: 'admin@example.com', - default: '', - }, - { - displayName: 'To Addresses', - name: 'toAddresses', - type: 'string', - description: 'Email addresses of the recipients.', - typeOptions: { - multipleValues: true, - multipleValueButtonText: 'Add To Email', - }, - displayOptions: { - show: { - operation: [ - 'sendEmail', - ], - }, - }, - placeholder: 'info@example.com', - default: [], - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { - show: { - operation: [ - 'sendEmail', - ], - }, - }, - options: [ - { - displayName: 'Bcc Addresses', - name: 'bccAddresses', - type: 'string', - typeOptions: { - multipleValues: true, - multipleValueButtonText: 'Add Bcc Email', - }, - description: 'Bcc Recipients of the email.', - default: [], - }, - { - displayName: 'Cc Addresses', - name: 'ccAddresses', - type: 'string', - typeOptions: { - multipleValues: true, - multipleValueButtonText: 'Add Cc Email', - }, - description: 'Cc recipients of the email.', - default: [], - }, - { - displayName: 'Configuration Set Name', - name: 'configurationSetName', - type: 'string', - description: 'Name of the configuration set to use when you send an email using SendEmail.', - default: '', - }, - { - displayName: 'Reply To Addresses', - name: 'replyToAddresses', - type: 'string', - typeOptions: { - multipleValues: true, - multipleValueButtonText: 'Add Reply To Email', - }, - placeholder: 'Add Reply Address', - description: 'Reply-to email address(es) for the message.', - default: [], - }, - { - displayName: 'Return Path', - name: 'returnPath', - type: 'string', - description: 'Email address that bounces and complaints will be forwarded to when feedback forwarding is enabled', - default: '', - }, - { - displayName: 'Return Path Arn', - name: 'returnPathArn', - type: 'string', - default: '', - description: 'This parameter is used only for sending authorization', - }, - { - displayName: 'Source Arn', - name: 'sourceArn', - type: 'string', - description: 'This parameter is used only for sending authorization.', - default: '', - }, - ], - }, - ], - }; - - async execute(this: IExecuteFunctions): Promise { - const items = this.getInputData(); - const returnData: IDataObject[] = []; - let responseData; - const operation = this.getNodeParameter('operation', 0) as string; - for (let i = 0; i < items.length; i++) { - if (operation === 'sendEmail') { - const toAddresses = this.getNodeParameter('toAddresses', i) as string[]; - const message = this.getNodeParameter('body', i) as string; - const subject = this.getNodeParameter('subject', i) as string; - const fromEmail = this.getNodeParameter('fromEmail', i) as string; - const isBodyHtml = this.getNodeParameter('isBodyHtml', i) as boolean; - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - const params = [ - `Message.Subject.Data=${subject}`, - `Source=${fromEmail}`, - ]; - if (isBodyHtml) { - params.push(`Message.Body.Html.Data=${message}`); - } else { - params.push(`Message.Body.Text.Data=${message}`); - } - if (toAddresses.length) { - setParameter(params, 'Destination.ToAddresses.member', toAddresses); - } else { - throw new Error('At least one "To Address" has to be added!'); - } - if (additionalFields.configurationSetName) { - params.push(`ConfigurationSetName=${additionalFields.configurationSetName}`); - } - if (additionalFields.returnPath) { - params.push(`ReturnPath=${additionalFields.returnPath}`); - } - if (additionalFields.returnPathArn) { - params.push(`ReturnPathArn=${additionalFields.returnPathArn}`); - } - if (additionalFields.sourceArn) { - params.push(`SourceArn=${additionalFields.sourceArn}`); - } - if (additionalFields.replyToAddresses) { - setParameter(params, 'ReplyToAddresses.member', additionalFields.replyToAddresses as string[]); - } - if (additionalFields.bccAddresses) { - setParameter(params, 'Destination.BccAddresses.member', additionalFields.bccAddresses as string[]); - } - if (additionalFields.ccAddressesUi) { - let ccAddresses = (additionalFields.ccAddressesUi as IDataObject).ccAddressesValues as string[]; - //@ts-ignore - ccAddresses = ccAddresses.map(o => o.address); - if (ccAddresses) { - setParameter(params, 'Destination.CcAddresses.member', ccAddresses); - } - } - - try { - responseData = await awsApiRequestSOAP.call(this, 'email', 'POST', '/?Action=SendEmail&' + params.join('&')); - } catch(err) { - throw new Error(err); - } - } - if (Array.isArray(responseData)) { - returnData.push.apply(returnData, responseData as IDataObject[]); - } else { - returnData.push(responseData as IDataObject); - } - } - return [this.helpers.returnJsonArray(returnData)]; - } -} diff --git a/packages/nodes-base/nodes/Aws/GenericFunctions.ts b/packages/nodes-base/nodes/Aws/GenericFunctions.ts index 880c0a02bb..c6bc36fe49 100644 --- a/packages/nodes-base/nodes/Aws/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Aws/GenericFunctions.ts @@ -29,6 +29,8 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I body: signOpts.body, }; + console.log(options); + try { return await this.helpers.request!(options); } catch (error) { diff --git a/packages/nodes-base/nodes/Aws/SES/AwsSes.node.ts b/packages/nodes-base/nodes/Aws/SES/AwsSes.node.ts new file mode 100644 index 0000000000..1eadf6da14 --- /dev/null +++ b/packages/nodes-base/nodes/Aws/SES/AwsSes.node.ts @@ -0,0 +1,848 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + ILoadOptionsFunctions, + INodeExecutionData, + INodePropertyOptions, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import { + awsApiRequestSOAP, + awsApiRequestSOAPAllItems, +} from './GenericFunctions'; + +function setParameter(params: string[], base: string, values: string[]) { + for (let i = 0; i < values.length; i++) { + params.push(`${base}.${i+1}=${values[i]}`); + } +} + +export class AwsSes implements INodeType { + description: INodeTypeDescription = { + displayName: 'AWS SES', + name: 'awsSes', + icon: 'file:ses.png', + group: ['output'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Sends data to AWS SES', + defaults: { + name: 'AWS SES', + color: '#FF9900', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'aws', + required: true, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Email', + value: 'email', + }, + { + name: 'Template', + value: 'template', + }, + ], + default: 'email', + description: 'The operation to perform.', + }, + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'email', + ], + }, + }, + options: [ + { + name: 'Send', + value: 'send', + }, + { + name: 'Send Template', + value: 'sendTemplate', + }, + ], + default: 'send', + description: 'The operation to perform.', + }, + { + displayName: 'Is Body HTML', + name: 'isBodyHtml', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'email', + ], + operation: [ + 'send', + ], + }, + }, + default: false, + description: 'If body is HTML or simple text.', + }, + { + displayName: 'Subject', + name: 'subject', + type: 'string', + displayOptions: { + show: { + resource: [ + 'email', + ], + operation: [ + 'send', + ], + }, + }, + default: '', + required: true, + }, + { + displayName: 'Body', + name: 'body', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + displayOptions: { + show: { + resource: [ + 'email', + ], + operation: [ + 'send', + ], + }, + }, + default: '', + description: 'The message to be sent.', + required: true, + }, + { + displayName: 'From Email', + name: 'fromEmail', + type: 'string', + displayOptions: { + show: { + resource: [ + 'email', + ], + operation: [ + 'send', + ], + }, + }, + required: true, + description: 'Email address of the sender.', + placeholder: 'admin@example.com', + default: '', + }, + { + displayName: 'To Addresses', + name: 'toAddresses', + type: 'string', + description: 'Email addresses of the recipients.', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add To Email', + }, + displayOptions: { + show: { + resource: [ + 'email', + ], + operation: [ + 'send', + ], + }, + }, + placeholder: 'info@example.com', + default: [], + }, + { + displayName: 'Template Name', + name: 'templateName', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getTemplates', + }, + displayOptions: { + show: { + resource: [ + 'email', + ], + operation: [ + 'sendTemplate', + ], + }, + }, + default: '', + description: 'The ARN of the template to use when sending this email', + }, + { + displayName: 'From Email', + name: 'fromEmail', + type: 'string', + displayOptions: { + show: { + resource: [ + 'email', + ], + operation: [ + 'sendTemplate', + ], + }, + }, + required: true, + description: 'Email address of the sender.', + placeholder: 'admin@example.com', + default: '', + }, + { + displayName: 'To Addresses', + name: 'toAddresses', + type: 'string', + description: 'Email addresses of the recipients.', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add To Email', + }, + displayOptions: { + show: { + resource: [ + 'email', + ], + operation: [ + 'sendTemplate', + ], + }, + }, + placeholder: 'info@example.com', + default: [], + }, + { + displayName: 'Template Data', + name: 'templateDataUi', + type: 'fixedCollection', + placeholder: 'Add Data', + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + resource: [ + 'email', + ], + operation: [ + 'sendTemplate', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Data', + name: 'templateDataValues', + values: [ + { + displayName: 'Key', + name: 'key', + type: 'string', + default: '', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'email', + ], + operation: [ + 'send', + 'sendTemplate', + ], + }, + }, + options: [ + { + displayName: 'Bcc Addresses', + name: 'bccAddresses', + type: 'string', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add Bcc Email', + }, + description: 'Bcc Recipients of the email.', + default: [], + }, + { + displayName: 'Cc Addresses', + name: 'ccAddresses', + type: 'string', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add Cc Email', + }, + description: 'Cc recipients of the email.', + default: [], + }, + { + displayName: 'Configuration Set Name', + name: 'configurationSetName', + type: 'string', + description: 'Name of the configuration set to use when you send an email using send.', + default: '', + }, + { + displayName: 'Reply To Addresses', + name: 'replyToAddresses', + type: 'string', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add Reply To Email', + }, + placeholder: 'Add Reply Address', + description: 'Reply-to email address(es) for the message.', + default: [], + }, + { + displayName: 'Return Path', + name: 'returnPath', + type: 'string', + description: 'Email address that bounces and complaints will be forwarded to when feedback forwarding is enabled', + default: '', + }, + { + displayName: 'Return Path ARN', + name: 'returnPathArn', + type: 'string', + default: '', + description: 'This parameter is used only for sending authorization', + }, + { + displayName: 'Source ARN', + name: 'sourceArn', + type: 'string', + description: 'This parameter is used only for sending authorization.', + default: '', + }, + ], + }, + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'template', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a template', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a template', + }, + { + name: 'Get', + value: 'get', + description: 'Get a template', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all templates', + }, + { + name: 'Update', + value: 'update', + description: 'Update a template', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, + { + displayName: 'Template Name', + name: 'templateName', + type: 'string', + displayOptions: { + show: { + resource: [ + 'template', + ], + operation: [ + 'update', + 'create', + 'get', + 'delete', + ], + }, + }, + required: true, + description: 'The name of the template.', + default: '', + }, + { + displayName: 'Subject Part', + name: 'subjectPart', + type: 'string', + displayOptions: { + show: { + resource: [ + 'template', + ], + operation: [ + 'create', + ], + }, + }, + description: 'The subject line of the email.', + default: '', + }, + { + displayName: 'Html Part', + name: 'htmlPart', + type: 'string', + displayOptions: { + show: { + resource: [ + 'template', + ], + operation: [ + 'create', + ], + }, + }, + description: 'The HTML body of the email.', + default: '', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'template', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Text Part', + name: 'textPart', + type: 'string', + description: 'The email body that will be visible to recipients whose email clients do not display HTML.', + default: '', + }, + ], + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'template', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Text Part', + name: 'textPart', + type: 'string', + description: 'The email body that will be visible to recipients whose email clients do not display HTML.', + default: '', + }, + { + displayName: 'Subject Part', + name: 'subjectPart', + type: 'string', + description: 'The subject line of the email.', + default: '', + }, + { + displayName: 'Html Part', + name: 'htmlPart', + type: 'string', + description: 'The HTML body of the email.', + default: '', + }, + ], + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'template', + ], + operation: [ + 'getAll', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 20, + displayOptions: { + show: { + resource: [ + 'template', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, + ], + }; + + methods = { + loadOptions: { + // Get all the available templates to display them to user so that he can + // select them easily + async getTemplates(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + + const templates = await awsApiRequestSOAPAllItems.call(this, 'ListTemplatesResponse.ListTemplatesResult.TemplatesMetadata.member', 'email', 'POST', '/?Action=ListTemplates'); + + for (const template of templates) { + const templateName = template.Name; + const templateId = template.Name; + + returnData.push({ + name: templateName, + value: templateId, + }); + } + + return returnData; + } + }, + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + let responseData; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + + for (let i = 0; i < items.length; i++) { + + if (resource === 'email') { + + if (operation === 'send') { + + const toAddresses = this.getNodeParameter('toAddresses', i) as string[]; + + const message = this.getNodeParameter('body', i) as string; + + const subject = this.getNodeParameter('subject', i) as string; + + const fromEmail = this.getNodeParameter('fromEmail', i) as string; + + const isBodyHtml = this.getNodeParameter('isBodyHtml', i) as boolean; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + const params = [ + `Message.Subject.Data=${subject}`, + `Source=${fromEmail}`, + ]; + + if (isBodyHtml) { + params.push(`Message.Body.Html.Data=${encodeURI(message)}`); + } else { + params.push(`Message.Body.Text.Data=${encodeURI(message)}`); + } + + if (toAddresses.length) { + setParameter(params, 'Destination.ToAddresses.member', toAddresses); + } else { + throw new Error('At least one "To Address" has to be added!'); + } + + if (additionalFields.configurationSetName) { + params.push(`ConfigurationSetName=${additionalFields.configurationSetName}`); + } + + if (additionalFields.returnPath) { + params.push(`ReturnPath=${additionalFields.returnPath}`); + } + + if (additionalFields.returnPathArn) { + params.push(`ReturnPathArn=${additionalFields.returnPathArn}`); + } + + if (additionalFields.sourceArn) { + params.push(`SourceArn=${additionalFields.sourceArn}`); + } + + if (additionalFields.replyToAddresses) { + setParameter(params, 'ReplyToAddresses.member', additionalFields.replyToAddresses as string[]); + } + + if (additionalFields.bccAddresses) { + setParameter(params, 'Destination.BccAddresses.member', additionalFields.bccAddresses as string[]); + } + + if (additionalFields.ccAddressesUi) { + let ccAddresses = (additionalFields.ccAddressesUi as IDataObject).ccAddressesValues as string[]; + //@ts-ignore + ccAddresses = ccAddresses.map(o => o.address); + if (ccAddresses) { + setParameter(params, 'Destination.CcAddresses.member', ccAddresses); + } + } + + responseData = await awsApiRequestSOAP.call(this, 'email', 'POST', '/?Action=send&' + params.join('&')); + } + + if (operation === 'sendTemplate') { + + const toAddresses = this.getNodeParameter('toAddresses', i) as string[]; + + const template = this.getNodeParameter('templateName', i) as string; + + const fromEmail = this.getNodeParameter('fromEmail', i) as string; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + const templateDataUi = this.getNodeParameter('templateDataUi', i) as IDataObject; + + const params = [ + `Template=${template}`, + `Source=${fromEmail}`, + ]; + + if (toAddresses.length) { + setParameter(params, 'Destination.ToAddresses.member', toAddresses); + } else { + throw new Error('At least one "To Address" has to be added!'); + } + + if (additionalFields.configurationSetName) { + params.push(`ConfigurationSetName=${additionalFields.configurationSetName}`); + } + + if (additionalFields.returnPath) { + params.push(`ReturnPath=${additionalFields.returnPath}`); + } + + if (additionalFields.returnPathArn) { + params.push(`ReturnPathArn=${additionalFields.returnPathArn}`); + } + + if (additionalFields.sourceArn) { + params.push(`SourceArn=${additionalFields.sourceArn}`); + } + + if (additionalFields.replyToAddresses) { + setParameter(params, 'ReplyToAddresses.member', additionalFields.replyToAddresses as string[]); + } + + if (additionalFields.bccAddresses) { + setParameter(params, 'Destination.BccAddresses.member', additionalFields.bccAddresses as string[]); + } + + if (additionalFields.ccAddressesUi) { + let ccAddresses = (additionalFields.ccAddressesUi as IDataObject).ccAddressesValues as string[]; + //@ts-ignore + ccAddresses = ccAddresses.map(o => o.address); + if (ccAddresses) { + setParameter(params, 'Destination.CcAddresses.member', ccAddresses); + } + } + + if (templateDataUi) { + const templateDataValues = (templateDataUi as IDataObject).templateDataValues as IDataObject[]; + const templateData: IDataObject = {}; + for (const key of Object.keys(templateDataValues)) { + //@ts-ignore + templateData[key]= templateDataValues[key]; + } + params.push(`TemplateData=${JSON.stringify(templateData)}`); + } + + responseData = await awsApiRequestSOAP.call(this, 'email', 'POST', '/?Action=SendTemplatedEmail&' + params.join('&')); + + responseData = responseData.SendTemplatedEmailResponse; + } + } + + if (resource === 'template') { + + if (operation === 'create') { + + const templateName = this.getNodeParameter('templateName', i) as string; + + const subjectPart = this.getNodeParameter('subjectPart', i) as string; + + const htmlPart = this.getNodeParameter('htmlPart', i) as string; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + const params = [ + `Template.TemplateName=${templateName}`, + `Template.SubjectPart=${subjectPart}`, + `Template.HtmlPart=

${htmlPart}

` + ]; + + if (additionalFields.textPart) { + params.push(`Template.TextPart=${additionalFields.textPart}`); + } + + responseData = await awsApiRequestSOAP.call(this, 'email', 'POST', '/?Action=CreateTemplate&' + params.join('&')); + + responseData = responseData.CreateTemplateResponse; + } + + if (operation === 'delete') { + + const templateName = this.getNodeParameter('templateName', i) as string; + + const params = [ + `TemplateName=${templateName}`, + ]; + + responseData = await awsApiRequestSOAP.call(this, 'email', 'POST', '/?Action=DeleteTemplate&' + params.join('&')); + + responseData = responseData.DeleteTemplateResponse; + } + + if (operation === 'get') { + + const templateName = this.getNodeParameter('templateName', i) as string; + + const params = [ + `TemplateName=${templateName}`, + ]; + + responseData = await awsApiRequestSOAP.call(this, 'email', 'POST', '/?Action=GetTemplate&' + params.join('&')); + + responseData = responseData.GetTemplateResponse; + } + + if (operation === 'getAll') { + + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + + if (returnAll === true) { + responseData = await awsApiRequestSOAPAllItems.call(this, 'ListTemplatesResponse.ListTemplatesResult.TemplatesMetadata.member', 'email', 'POST', '/?Action=ListTemplates'); + + } else { + const limit = this.getNodeParameter('limit', i) as number; + + responseData = await awsApiRequestSOAP.call(this, 'email', 'GET', `/?Action=ListTemplates&MaxItems=${limit}`); + + responseData = responseData.ListTemplatesResponse.ListTemplatesResult.TemplatesMetadata.member; + } + } + + if (operation === 'update') { + + const templateName = this.getNodeParameter('templateName', i) as string; + + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + + const params = [ + `Template.TemplateName=${templateName}`, + ]; + + if (updateFields.textPart) { + params.push(`Template.TextPart=${updateFields.textPart}`); + } + + if (updateFields.subjectPart) { + params.push(`Template.SubjectPart=${updateFields.subjectPart}`); + } + + if (updateFields.subjectPart) { + params.push(`Template.HtmlPart=${updateFields.htmlPart}`); + } + + responseData = await awsApiRequestSOAP.call(this, 'email', 'POST', '/?Action=UpdateTemplate&' + params.join('&')); + + responseData = responseData.UpdateTemplateResponse; + } + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + + } else { + returnData.push(responseData as IDataObject); + } + } + + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Aws/SES/GenericFunctions.ts b/packages/nodes-base/nodes/Aws/SES/GenericFunctions.ts new file mode 100644 index 0000000000..b22ee7ca63 --- /dev/null +++ b/packages/nodes-base/nodes/Aws/SES/GenericFunctions.ts @@ -0,0 +1,116 @@ +import { + sign, +} from 'aws4'; + +import { + OptionsWithUri, +} from 'request'; + +import { + parseString, +} from 'xml2js'; + +import { + IExecuteFunctions, + IHookFunctions, + ILoadOptionsFunctions, + IWebhookFunctions, +} from 'n8n-core'; + +import { + IDataObject, +} from 'n8n-workflow'; + +import { + get, +} from 'lodash'; + +export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string, headers?: object): Promise { // tslint:disable-line:no-any + const credentials = this.getCredentials('aws'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const endpoint = `${service}.${credentials.region}.amazonaws.com`; + + // Sign AWS API request with the user credentials + const signOpts = { headers: headers || {}, host: endpoint, method, path, body }; + sign(signOpts, { accessKeyId: `${credentials.accessKeyId}`.trim(), secretAccessKey: `${credentials.secretAccessKey}`.trim() }); + + const options: OptionsWithUri = { + headers: signOpts.headers, + method, + uri: `https://${endpoint}${signOpts.path}`, + body: signOpts.body, + }; + + try { + return await this.helpers.request!(options); + } catch (error) { + const errorMessage = error.response.body.message || error.response.body.Message || error.message; + + if (error.statusCode === 403) { + if (errorMessage === 'The security token included in the request is invalid.') { + throw new Error('The AWS credentials are not valid!'); + } else if (errorMessage.startsWith('The request signature we calculated does not match the signature you provided')) { + throw new Error('The AWS credentials are not valid!'); + } + } + + throw new Error(`AWS error response [${error.statusCode}]: ${errorMessage}`); + } +} + +export async function awsApiRequestREST(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, service: string, method: string, path: string, body?: string, headers?: object): Promise { // tslint:disable-line:no-any + const response = await awsApiRequest.call(this, service, method, path, body, headers); + try { + return JSON.parse(response); + } catch (e) { + return response; + } +} + +export async function awsApiRequestSOAP(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string, headers?: object): Promise { // tslint:disable-line:no-any + const response = await awsApiRequest.call(this, service, method, path, body, headers); + try { + return await new Promise((resolve, reject) => { + parseString(response, { explicitArray: false }, (err, data) => { + if (err) { + return reject(err); + } + resolve(data); + }); + }); + } catch (e) { + return response; + } +} + + +export async function awsApiRequestSOAPAllItems(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, propertyName: string, service: string, method: string, path: string, body?: string, query: IDataObject = {}, headers: IDataObject = {}, option: IDataObject = {}, region?: string): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + + const propertyNameArray = propertyName.split('.'); + + do { + responseData = await awsApiRequestSOAP.call(this, service, method, path, body, query); + + if (get(responseData, `${propertyNameArray[0]}.${propertyNameArray[1]}.NextToken`)) { + query['NextToken'] = get(responseData, `${propertyNameArray[0]}.${propertyNameArray[1]}.NextToken`); + } + if (get(responseData, propertyName)) { + if (Array.isArray(get(responseData, propertyName))) { + returnData.push.apply(returnData, get(responseData, propertyName)); + } else { + returnData.push(get(responseData, propertyName)); + } + } + } while ( + get(responseData, `${propertyNameArray[0]}.${propertyNameArray[1]}.NextToken`) !== undefined + ); + + return returnData; +} diff --git a/packages/nodes-base/nodes/Aws/ses.png b/packages/nodes-base/nodes/Aws/SES/ses.png similarity index 100% rename from packages/nodes-base/nodes/Aws/ses.png rename to packages/nodes-base/nodes/Aws/SES/ses.png diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 331fac8bcc..83d02ac569 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -205,7 +205,7 @@ "dist/nodes/Affinity/AffinityTrigger.node.js", "dist/nodes/Aws/AwsLambda.node.js", "dist/nodes/Aws/S3/AwsS3.node.js", - "dist/nodes/Aws/AwsSes.node.js", + "dist/nodes/Aws/SES/AwsSes.node.js", "dist/nodes/Aws/AwsSns.node.js", "dist/nodes/Aws/AwsSnsTrigger.node.js", "dist/nodes/Bannerbear/Bannerbear.node.js",