mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-14 00:24:07 -08:00
74cedd94a8
* add sendinblue svg icon * Add code and required files for new sendinblue node * Add node to package.json * Update credentials to display API Key instead of Access Token * Use new svg found in brandfetch * ⚡ Improvements * ♻️ Moved descriptions for email to it's own file * ⚡ Added support for contact get * ⚡ moved email descriptions to it's own file * ⚡ Add logic to conditionally remove/format sms,email * ⚡ Improvements * ⚡ Refactor Sender descriptions to it's own file * ⚡ Fix urls * ⚡ Improvements attempt * ⚡ Refactor remove inline descriptions * ⚡ Minor improvement * 🎨 Learn a nice way to send options as key-value * ⚡ Improvements * ♻️ Fix Create Operation structure * ♻️ Refactor create functionality for attribute ♻️ Introduce override for createAttribute selectedCategory ♻️ Add delete functionality * 🔥 Remove preSend from delete * ⚡ Implement override for body types * ⚡ Cleanup node file * ⚡ Update response for contact update ⚡ Update request url for contact delete * ⚡ Add presend check for optional properties that are empty ⚡ Add Model file and TransactionalEmail interface * ⚡ formatting * ♻️ Remove requestOperations from Node Description level * ♻️ Cleanup routing for Get All ♻️ Make Identifier required * ⚡ Formatting * ♻️ Add Options Collection * ♻️ Add Filters area * ♻️ Formatting * ♻️ Handle empty return * ♻️ Remove unused code * ♻️ Fix pagination ♻️ Fix empty return for delete * ⚡ Add pagination * ⚡ Fix Modified Since * ♻️ Reorder send operation ui * ⚡ Remove no longer needed presend ⚡ Add send html template operation * ♻️ Make Contact Attribute name and type required * ♻️ Rename Attribute to Contact Attribute * ♻️ Rename Identifier to Contact Identifier * ♻️ Remove SMS from root level because it can exist in Contact Attributes * ♻️ Fix Array type using 'Array<T>' ♻️ Fix double quotes should be single quotes * 👕 Lint Fix * ⚡ Add email attachment functionality ⚡ Add attachment data validation * ⚡ Add dynamic loading of Email Template IDs * ♻️ Cleanup validation method * ⚡ Introduce workaround and use binary data for attachments * feat: Migrated to npm release of riot-tmpl fork. * 👕 Lint fix rules * 👕 Lint fix rules * fix: Updated imports to use @n8n_io/riot-tmpl * fix: Fixed Logger.ts types. * ⚡ Fix mixmatch of filename and package.json credentials list * ⚡ fix mixmatch in nodes list * feat(core): Give access to getBinaryDataBuffer in preSend method * ⚡ clean up mixmatches in node naming * ♻️ Refactor code to use newly exposed getBinaryDataBuffer method * ⚡ Improvements * 🔥 Remove unnecessary lines * 👕 Fix linting issues * ⚡ Fix issues with up to date APIs and improve readability * ⚡ update naming of files * ♻️ Move sendHtml boolean above subject ♻️ Update naming from Parameters to Fields * ♻️ Move sendHtml boolean above subject ♻️ Update naming from Parameters to Fields * ♻️ Add attribute name url encoding ♻️ Change limit's default to 50 * ⚡ Fix default for templateId * ⚡ Fix display name for attribute list * ♻️ Add clarity to attribute value display name * ♻️ Add tags and attachments for emails * ♻️ Add use of item's binary data fileName * 👕 Fix action lint rule * 👕 Remove deprecated lint rule * ⬆️ Update eslint-plugin-n8n-nodes-base * 👕 Fix lint rule for file name * ⚡ Fix update attribute * ♻️ Add upsert capabilites * 🔥 Remove create or update operation * ♻️ Add sendInBlueWebhookApi namespace * ♻️ Add Webhook API functionality * ⚡ Add SendInBlue Trigger * ⚡ Return correct webhookId data * ⚡ Add placeholder for receiving data * 👕 Fixing existing linting issues * 🚨 Enable namespacing in tslint file * 👕 Fix linting issues * ⚡ Rename exported WebhookApi * 🔥 Remove unused Model.ts file * ♻️ Update node to use SendInBlue namespace * ⚡ Revert back to allowing upsert functionality * ♻️ Fix options to better describe events * Remove update flag for create operation * ♻️ Fix discrepancies for contact resource * remove no-namespace lint rule * 👕 Fix linting issues * ♻️ Add sendInBlueWebhookApi namespace * ♻️ Add Webhook API functionality * ⚡ Add SendInBlue Trigger * ⚡ Return correct webhookId data * ⚡ Add placeholder for receiving data * 👕 Fix linting issues * ⚡ Rename exported WebhookApi * ♻️ Fix options to better describe events * Add optionswithuri import that was lost * ⚡ Fix details from janober's review * ⚡ Fix order of displayName and name properties * ⚡ Fix default value and improve loadOptions * ⚡ Introduce support for comma separated attribute values * ⚡ Introduce support for comma separated attribute values * 👕 Fix linting issues * Update defaults and required props * ⚡ Fix copy paste issue Upsert was not using correct endpoint * ⚡ Fix upsert email field display name * ⚡ Last update, upsert email description * ⚡ Add PostReceived type limit Co-authored-by: ricardo <ricardoespinoza105@gmail.com> Co-authored-by: Alex Grozav <alex@grozav.com> Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
389 lines
9.8 KiB
TypeScript
389 lines
9.8 KiB
TypeScript
import {
|
|
IExecuteSingleFunctions,
|
|
IHookFunctions,
|
|
IHttpRequestOptions,
|
|
IWebhookFunctions,
|
|
JsonObject,
|
|
NodeOperationError,
|
|
} from 'n8n-workflow';
|
|
import { OptionsWithUri } from 'request';
|
|
import MailComposer from 'nodemailer/lib/mail-composer';
|
|
export namespace SendInBlueNode {
|
|
type ValidEmailFields = { to: string } | { sender: string } | { cc: string } | { bcc: string };
|
|
type Address = { address: string; name?: string };
|
|
type Email = { email: string; name?: string };
|
|
type ToEmail = { to: Email[] };
|
|
type SenderEmail = { sender: Email };
|
|
type CCEmail = { cc: Email[] };
|
|
type BBCEmail = { bbc: Email[] };
|
|
type ValidatedEmail = ToEmail | SenderEmail | CCEmail | BBCEmail;
|
|
|
|
enum OVERRIDE_MAP_VALUES {
|
|
'CATEGORY' = 'category',
|
|
'NORMAL' = 'boolean',
|
|
'TRANSACTIONAL' = 'id',
|
|
}
|
|
|
|
enum OVERRIDE_MAP_TYPE {
|
|
'CATEGORY' = 'category',
|
|
'NORMAL' = 'normal',
|
|
'TRANSACTIONAL' = 'transactional',
|
|
}
|
|
|
|
export const INTERCEPTORS = new Map<string, (body: JsonObject) => void>([
|
|
[
|
|
OVERRIDE_MAP_TYPE.CATEGORY,
|
|
(body: JsonObject) => {
|
|
body!.type = OVERRIDE_MAP_VALUES.CATEGORY;
|
|
},
|
|
],
|
|
[
|
|
OVERRIDE_MAP_TYPE.NORMAL,
|
|
(body: JsonObject) => {
|
|
body!.type = OVERRIDE_MAP_VALUES.NORMAL;
|
|
},
|
|
],
|
|
[
|
|
OVERRIDE_MAP_TYPE.TRANSACTIONAL,
|
|
(body: JsonObject) => {
|
|
body!.type = OVERRIDE_MAP_VALUES.TRANSACTIONAL;
|
|
},
|
|
],
|
|
]);
|
|
export namespace Validators {
|
|
export async function validateAndCompileAttachmentsData(
|
|
this: IExecuteSingleFunctions,
|
|
requestOptions: IHttpRequestOptions,
|
|
): Promise<IHttpRequestOptions> {
|
|
const dataPropertyList = this.getNodeParameter(
|
|
'additionalFields.emailAttachments.attachment',
|
|
) as JsonObject;
|
|
const { body } = requestOptions;
|
|
|
|
const { attachment = [] } = body as { attachment: Array<{ content: string; name: string }> };
|
|
|
|
try {
|
|
const { binaryPropertyName } = dataPropertyList;
|
|
const dataMappingList = (binaryPropertyName as string).split(',');
|
|
for (const attachmentDataName of dataMappingList) {
|
|
const binaryPropertyName = attachmentDataName;
|
|
|
|
const item = this.getInputData();
|
|
|
|
if (item.binary![binaryPropertyName as string] === undefined) {
|
|
throw new NodeOperationError(
|
|
this.getNode(),
|
|
`No binary data property “${binaryPropertyName}” exists on item!`,
|
|
);
|
|
}
|
|
|
|
const bufferFromIncomingData = (await this.helpers.getBinaryDataBuffer(
|
|
binaryPropertyName,
|
|
)) as Buffer;
|
|
|
|
const {
|
|
data: content,
|
|
mimeType,
|
|
fileName,
|
|
fileExtension,
|
|
} = await this.helpers.prepareBinaryData(bufferFromIncomingData);
|
|
|
|
const itemIndex = this.getItemIndex();
|
|
const name = getFileName(
|
|
itemIndex,
|
|
mimeType,
|
|
fileExtension,
|
|
fileName || item.binary!.data.fileName,
|
|
);
|
|
|
|
attachment.push({ content, name });
|
|
}
|
|
|
|
Object.assign(body!, { attachment });
|
|
|
|
return requestOptions;
|
|
} catch (err) {
|
|
throw new NodeOperationError(this.getNode(), `${err}`);
|
|
}
|
|
}
|
|
|
|
export async function validateAndCompileTags(
|
|
this: IExecuteSingleFunctions,
|
|
requestOptions: IHttpRequestOptions,
|
|
): Promise<IHttpRequestOptions> {
|
|
const { tag } = this.getNodeParameter('additionalFields.emailTags.tags') as JsonObject;
|
|
const tags = (tag as string)
|
|
.split(',')
|
|
.map((tag) => tag.trim())
|
|
.filter((tag) => {
|
|
return tag !== '';
|
|
});
|
|
const { body } = requestOptions;
|
|
Object.assign(body!, { tags });
|
|
return requestOptions;
|
|
}
|
|
|
|
export async function validateAndCompileCCEmails(
|
|
this: IExecuteSingleFunctions,
|
|
requestOptions: IHttpRequestOptions,
|
|
): Promise<IHttpRequestOptions> {
|
|
const ccData = this.getNodeParameter(
|
|
'additionalFields.receipientsCC.receipientCc',
|
|
) as JsonObject;
|
|
const { cc } = ccData;
|
|
const { body } = requestOptions;
|
|
const data = validateEmailStrings({ cc: cc as string });
|
|
Object.assign(body!, data);
|
|
|
|
return requestOptions;
|
|
}
|
|
|
|
export async function validateAndCompileBCCEmails(
|
|
this: IExecuteSingleFunctions,
|
|
requestOptions: IHttpRequestOptions,
|
|
): Promise<IHttpRequestOptions> {
|
|
const bccData = this.getNodeParameter(
|
|
'additionalFields.receipientsBCC.receipientBcc',
|
|
) as JsonObject;
|
|
const { bcc } = bccData;
|
|
const { body } = requestOptions;
|
|
const data = validateEmailStrings({ bcc: bcc as string });
|
|
Object.assign(body!, data);
|
|
|
|
return requestOptions;
|
|
}
|
|
|
|
export async function validateAndCompileReceipientEmails(
|
|
this: IExecuteSingleFunctions,
|
|
requestOptions: IHttpRequestOptions,
|
|
): Promise<IHttpRequestOptions> {
|
|
const to = this.getNodeParameter('receipients') as string;
|
|
const { body } = requestOptions;
|
|
const data = validateEmailStrings({ to });
|
|
Object.assign(body!, data);
|
|
|
|
return requestOptions;
|
|
}
|
|
|
|
export async function validateAndCompileSenderEmail(
|
|
this: IExecuteSingleFunctions,
|
|
requestOptions: IHttpRequestOptions,
|
|
): Promise<IHttpRequestOptions> {
|
|
const sender = this.getNodeParameter('sender') as string;
|
|
const { body } = requestOptions;
|
|
const data = validateEmailStrings({ sender });
|
|
Object.assign(body!, data);
|
|
|
|
return requestOptions;
|
|
}
|
|
|
|
export async function validateAndCompileTemplateParameters(
|
|
this: IExecuteSingleFunctions,
|
|
requestOptions: IHttpRequestOptions,
|
|
): Promise<IHttpRequestOptions> {
|
|
const parameterData = this.getNodeParameter(
|
|
'additionalFields.templateParameters.parameterValues',
|
|
);
|
|
const { body } = requestOptions;
|
|
const { parmeters } = parameterData as JsonObject;
|
|
const params = (parmeters as string)
|
|
.split(',')
|
|
.filter((parameter) => {
|
|
return parameter.split('=').length === 2;
|
|
})
|
|
.map((parameter) => {
|
|
const [key, value] = parameter.split('=');
|
|
return {
|
|
[key]: value,
|
|
};
|
|
})
|
|
.reduce((obj, cObj) => {
|
|
Object.assign(obj, cObj);
|
|
return obj;
|
|
}, {});
|
|
|
|
Object.assign(body!, { params });
|
|
return requestOptions;
|
|
}
|
|
|
|
function validateEmailStrings(input: ValidEmailFields): ValidatedEmail {
|
|
const composer = new MailComposer({ ...input });
|
|
const addressFields = composer.compile().getAddresses();
|
|
|
|
const fieldFetcher = new Map<string, () => Email[] | Email>([
|
|
[
|
|
'bcc',
|
|
() => {
|
|
return (addressFields.bcc as unknown as Address[])?.map(formatToEmailName);
|
|
},
|
|
],
|
|
[
|
|
'cc',
|
|
() => {
|
|
return (addressFields.cc as unknown as Address[])?.map(formatToEmailName);
|
|
},
|
|
],
|
|
[
|
|
'from',
|
|
() => {
|
|
return (addressFields.from as unknown as Address[])?.map(formatToEmailName);
|
|
},
|
|
],
|
|
[
|
|
'reply-to',
|
|
() => {
|
|
return (addressFields['reply-to'] as unknown as Address[])?.map(formatToEmailName);
|
|
},
|
|
],
|
|
[
|
|
'sender',
|
|
() => {
|
|
return (addressFields.sender as unknown as Address[])?.map(formatToEmailName)[0];
|
|
},
|
|
],
|
|
[
|
|
'to',
|
|
() => {
|
|
return (addressFields.to as unknown as Address[])?.map(formatToEmailName);
|
|
},
|
|
],
|
|
]);
|
|
|
|
const result: { [key in keyof ValidatedEmail]: Email[] | Email } = {} as ValidatedEmail;
|
|
Object.keys(input).reduce((obj: { [key: string]: Email[] | Email }, key: string) => {
|
|
const getter = fieldFetcher.get(key);
|
|
const value = getter!();
|
|
obj[key] = value;
|
|
return obj;
|
|
}, result);
|
|
|
|
return result as ValidatedEmail;
|
|
}
|
|
}
|
|
|
|
function getFileName(
|
|
itemIndex: number,
|
|
mimeType: string,
|
|
fileExt: string,
|
|
fileName: string,
|
|
): string {
|
|
let ext = fileExt;
|
|
if (fileExt === undefined) {
|
|
ext = mimeType.split('/')[1];
|
|
}
|
|
|
|
let name = `${fileName}.${ext}`;
|
|
if (fileName === undefined) {
|
|
name = `file-${itemIndex}.${ext}`;
|
|
}
|
|
return name;
|
|
}
|
|
|
|
function formatToEmailName(data: Address): Email {
|
|
const { address: email, name } = data;
|
|
const result = { email };
|
|
if (name !== undefined && name !== '') {
|
|
Object.assign(result, { name });
|
|
}
|
|
return { ...result };
|
|
}
|
|
}
|
|
|
|
export namespace SendInBlueWebhookApi {
|
|
interface WebhookDetails {
|
|
url: string;
|
|
id: number;
|
|
description: string;
|
|
events: string[];
|
|
type: string;
|
|
createdAt: string;
|
|
modifiedAt: string;
|
|
}
|
|
|
|
interface WebhookId {
|
|
id: string;
|
|
}
|
|
|
|
interface Webhooks {
|
|
webhooks: WebhookDetails[];
|
|
}
|
|
|
|
const credentialsName = 'sendinblueApi';
|
|
const baseURL = 'https://api.sendinblue.com/v3';
|
|
export const supportedAuthMap = new Map<string, (ref: IWebhookFunctions) => Promise<string>>([
|
|
[
|
|
'apiKey',
|
|
async (ref: IWebhookFunctions): Promise<string> => {
|
|
const credentials = await ref.getCredentials(credentialsName);
|
|
return credentials.sharedSecret as string;
|
|
},
|
|
],
|
|
]);
|
|
|
|
export const fetchWebhooks = async (ref: IHookFunctions, type: string): Promise<Webhooks> => {
|
|
const endpoint = `${baseURL}/webhooks?type=${type}`;
|
|
|
|
const options: OptionsWithUri = {
|
|
method: 'GET',
|
|
headers: {
|
|
Accept: 'application/json',
|
|
},
|
|
uri: endpoint,
|
|
};
|
|
|
|
const webhooks = (await ref.helpers.requestWithAuthentication.call(
|
|
ref,
|
|
credentialsName,
|
|
options,
|
|
)) as string;
|
|
|
|
return JSON.parse(webhooks) as Webhooks;
|
|
};
|
|
|
|
export const createWebHook = async (
|
|
ref: IHookFunctions,
|
|
type: string,
|
|
events: string[],
|
|
url: string,
|
|
): Promise<WebhookId> => {
|
|
const endpoint = `${baseURL}/webhooks`;
|
|
|
|
const options: OptionsWithUri = {
|
|
method: 'POST',
|
|
headers: {
|
|
Accept: 'application/json',
|
|
},
|
|
uri: endpoint,
|
|
body: {
|
|
events,
|
|
type,
|
|
url,
|
|
},
|
|
};
|
|
|
|
const webhookId = await ref.helpers.requestWithAuthentication.call(
|
|
ref,
|
|
credentialsName,
|
|
options,
|
|
);
|
|
|
|
return JSON.parse(webhookId) as WebhookId;
|
|
};
|
|
|
|
export const deleteWebhook = async (ref: IHookFunctions, webhookId: string) => {
|
|
const endpoint = `${baseURL}/webhooks/${webhookId}`;
|
|
const body = {};
|
|
|
|
const options: OptionsWithUri = {
|
|
method: 'DELETE',
|
|
headers: {
|
|
Accept: 'application/json',
|
|
},
|
|
uri: endpoint,
|
|
body,
|
|
};
|
|
|
|
return await ref.helpers.requestWithAuthentication.call(ref, credentialsName, options);
|
|
};
|
|
}
|