mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
feat(WhatsApp Trigger Node): New option to opt-out from message status updates (#13751)
This commit is contained in:
@@ -8,7 +8,6 @@ import {
|
||||
type IWebhookFunctions,
|
||||
type IWebhookResponseData,
|
||||
NodeConnectionType,
|
||||
type INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
@@ -18,57 +17,30 @@ import {
|
||||
} from './GenericFunctions';
|
||||
import type { WhatsAppPageEvent } from './types';
|
||||
|
||||
const whatsappTriggerDescription: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Trigger On',
|
||||
name: 'updates',
|
||||
type: 'multiOptions',
|
||||
required: true,
|
||||
default: [],
|
||||
options: [
|
||||
{
|
||||
name: 'Account Review Update',
|
||||
value: 'account_review_update',
|
||||
},
|
||||
{
|
||||
name: 'Account Update',
|
||||
value: 'account_update',
|
||||
},
|
||||
{
|
||||
name: 'Business Capability Update',
|
||||
value: 'business_capability_update',
|
||||
},
|
||||
{
|
||||
name: 'Message Template Quality Update',
|
||||
value: 'message_template_quality_update',
|
||||
},
|
||||
{
|
||||
name: 'Message Template Status Update',
|
||||
value: 'message_template_status_update',
|
||||
},
|
||||
{
|
||||
name: 'Messages',
|
||||
value: 'messages',
|
||||
},
|
||||
{
|
||||
name: 'Phone Number Name Update',
|
||||
value: 'phone_number_name_update',
|
||||
},
|
||||
{
|
||||
name: 'Phone Number Quality Update',
|
||||
value: 'phone_number_quality_update',
|
||||
},
|
||||
{
|
||||
name: 'Security',
|
||||
value: 'security',
|
||||
},
|
||||
{
|
||||
name: 'Template Category Update',
|
||||
value: 'template_category_update',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
export const filterStatuses = (
|
||||
events: Array<{ statuses?: Array<{ status: string }> }>,
|
||||
allowedStatuses: string[] | undefined,
|
||||
) => {
|
||||
if (!allowedStatuses) return events;
|
||||
|
||||
// If allowedStatuses is empty filter out events with statuses
|
||||
if (!allowedStatuses.length) {
|
||||
return events.filter((event) => (event?.statuses ? false : true));
|
||||
}
|
||||
|
||||
// If 'all' is not in allowedStatuses, return only events with allowed status
|
||||
if (!allowedStatuses.includes('all')) {
|
||||
return events.filter((event) => {
|
||||
const statuses = event.statuses;
|
||||
if (statuses?.length) {
|
||||
return statuses.some((status) => allowedStatuses.includes(status.status));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
return events;
|
||||
};
|
||||
|
||||
export class WhatsAppTrigger implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
@@ -112,7 +84,99 @@ export class WhatsAppTrigger implements INodeType {
|
||||
type: 'notice',
|
||||
default: '',
|
||||
},
|
||||
...whatsappTriggerDescription,
|
||||
{
|
||||
displayName: 'Trigger On',
|
||||
name: 'updates',
|
||||
type: 'multiOptions',
|
||||
required: true,
|
||||
default: [],
|
||||
options: [
|
||||
{
|
||||
name: 'Account Review Update',
|
||||
value: 'account_review_update',
|
||||
},
|
||||
{
|
||||
name: 'Account Update',
|
||||
value: 'account_update',
|
||||
},
|
||||
{
|
||||
name: 'Business Capability Update',
|
||||
value: 'business_capability_update',
|
||||
},
|
||||
{
|
||||
name: 'Message Template Quality Update',
|
||||
value: 'message_template_quality_update',
|
||||
},
|
||||
{
|
||||
name: 'Message Template Status Update',
|
||||
value: 'message_template_status_update',
|
||||
},
|
||||
{
|
||||
name: 'Messages',
|
||||
value: 'messages',
|
||||
},
|
||||
{
|
||||
name: 'Phone Number Name Update',
|
||||
value: 'phone_number_name_update',
|
||||
},
|
||||
{
|
||||
name: 'Phone Number Quality Update',
|
||||
value: 'phone_number_quality_update',
|
||||
},
|
||||
{
|
||||
name: 'Security',
|
||||
value: 'security',
|
||||
},
|
||||
{
|
||||
name: 'Template Category Update',
|
||||
value: 'template_category_update',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
default: {},
|
||||
placeholder: 'Add option',
|
||||
options: [
|
||||
{
|
||||
// https://developers.facebook.com/docs/whatsapp/cloud-api/webhooks/payload-examples#message-status-updates
|
||||
displayName: 'Receive Message Status Updates',
|
||||
name: 'messageStatusUpdates',
|
||||
type: 'multiOptions',
|
||||
default: ['all'],
|
||||
description:
|
||||
'WhatsApp sends notifications to the Trigger when the status of a message changes (for example from Sent to Delivered and from Delivered to Read). To avoid multiple executions for one WhatsApp message, you can set the Trigger to execute only on selected message status updates.',
|
||||
options: [
|
||||
{
|
||||
name: 'All',
|
||||
value: 'all',
|
||||
},
|
||||
{
|
||||
name: 'Deleted',
|
||||
value: 'deleted',
|
||||
},
|
||||
{
|
||||
name: 'Delivered',
|
||||
value: 'delivered',
|
||||
},
|
||||
{
|
||||
name: 'Failed',
|
||||
value: 'failed',
|
||||
},
|
||||
{
|
||||
name: 'Read',
|
||||
value: 'read',
|
||||
},
|
||||
{
|
||||
name: 'Sent',
|
||||
value: 'sent',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -228,12 +292,14 @@ export class WhatsAppTrigger implements INodeType {
|
||||
.map((change) => ({ ...change.value, field: change.field })),
|
||||
);
|
||||
|
||||
if (events.length === 0) {
|
||||
return {};
|
||||
}
|
||||
const options = this.getNodeParameter('options', {}) as { messageStatusUpdates?: string[] };
|
||||
|
||||
const returnData = filterStatuses(events, options.messageStatusUpdates);
|
||||
|
||||
if (returnData.length === 0) return {};
|
||||
|
||||
return {
|
||||
workflowData: [this.helpers.returnJsonArray(events)],
|
||||
workflowData: [this.helpers.returnJsonArray(returnData)],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { filterStatuses } from '../WhatsAppTrigger.node';
|
||||
|
||||
describe('filterStatuses', () => {
|
||||
const mockEvents = [
|
||||
{ statuses: [{ status: 'deleted' }] },
|
||||
{ statuses: [{ status: 'delivered' }] },
|
||||
{ statuses: [{ status: 'failed' }] },
|
||||
{ statuses: [{ status: 'read' }] },
|
||||
{ statuses: [{ status: 'sent' }] },
|
||||
];
|
||||
|
||||
test('returns events with no statuses when allowedStatuses is empty', () => {
|
||||
expect(filterStatuses(mockEvents, [])).toEqual([]);
|
||||
});
|
||||
|
||||
test("returns all events when 'all' is in allowedStatuses", () => {
|
||||
expect(filterStatuses(mockEvents, ['all'])).toEqual(mockEvents);
|
||||
});
|
||||
|
||||
test('filters events correctly when specific statuses are provided', () => {
|
||||
expect(filterStatuses(mockEvents, ['deleted', 'read'])).toEqual([
|
||||
{ statuses: [{ status: 'deleted' }] },
|
||||
{ statuses: [{ status: 'read' }] },
|
||||
]);
|
||||
});
|
||||
|
||||
test('returns only event with matching status', () => {
|
||||
expect(filterStatuses(mockEvents, ['failed'])).toEqual([{ statuses: [{ status: 'failed' }] }]);
|
||||
});
|
||||
|
||||
test('returns unchanged event when allowedStatuses is undefined', () => {
|
||||
expect(filterStatuses(mockEvents, undefined)).toEqual(mockEvents);
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { GenericValue, IDataObject } from 'n8n-workflow';
|
||||
import type { GenericValue } from 'n8n-workflow';
|
||||
|
||||
export type BaseFacebookResponse<TData> = { data: TData };
|
||||
export type BasePaginatedFacebookResponse<TData> = BaseFacebookResponse<TData> & {
|
||||
@@ -70,15 +70,15 @@ export interface WhatsAppPageEvent {
|
||||
entry: WhatsAppEventEntry[];
|
||||
}
|
||||
|
||||
export type WhatsAppEventChanges = Array<{
|
||||
field: string;
|
||||
value: { statuses?: Array<{ status: string }> };
|
||||
}>;
|
||||
|
||||
export interface WhatsAppEventEntry {
|
||||
id: string;
|
||||
time: number;
|
||||
changes: [
|
||||
{
|
||||
field: string;
|
||||
value: IDataObject;
|
||||
},
|
||||
];
|
||||
changes: WhatsAppEventChanges;
|
||||
}
|
||||
|
||||
export interface FacebookFormLeadData {
|
||||
|
||||
Reference in New Issue
Block a user