feat(WhatsApp Trigger Node): New option to opt-out from message status updates (#13751)

This commit is contained in:
Michael Kret
2025-03-11 11:27:58 +02:00
committed by GitHub
parent 6c1b99d6c2
commit 9c040ee5a5
3 changed files with 164 additions and 64 deletions

View File

@@ -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)],
};
}
}

View File

@@ -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);
});
});

View File

@@ -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 {