2022-08-03 09:08:51 -07:00
|
|
|
import {
|
|
|
|
IExecuteSingleFunctions,
|
|
|
|
IHookFunctions,
|
|
|
|
IHttpRequestOptions,
|
|
|
|
IWebhookFunctions,
|
|
|
|
JsonObject,
|
2022-10-21 11:52:43 -07:00
|
|
|
jsonParse,
|
2022-08-03 09:08:51 -07:00
|
|
|
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;
|
|
|
|
|
2022-10-21 11:52:43 -07:00
|
|
|
return jsonParse(webhooks) as Webhooks;
|
2022-08-03 09:08:51 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
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,
|
|
|
|
);
|
|
|
|
|
2022-10-21 11:52:43 -07:00
|
|
|
return jsonParse(webhookId) as WebhookId;
|
2022-08-03 09:08:51 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
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);
|
|
|
|
};
|
|
|
|
}
|