mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-22 12:19:09 +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 IWebhookFunctions,
|
||||||
type IWebhookResponseData,
|
type IWebhookResponseData,
|
||||||
NodeConnectionType,
|
NodeConnectionType,
|
||||||
type INodeProperties,
|
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -18,7 +17,73 @@ import {
|
|||||||
} from './GenericFunctions';
|
} from './GenericFunctions';
|
||||||
import type { WhatsAppPageEvent } from './types';
|
import type { WhatsAppPageEvent } from './types';
|
||||||
|
|
||||||
const whatsappTriggerDescription: INodeProperties[] = [
|
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 = {
|
||||||
|
displayName: 'WhatsApp Trigger',
|
||||||
|
name: 'whatsAppTrigger',
|
||||||
|
icon: 'file:whatsapp.svg',
|
||||||
|
group: ['trigger'],
|
||||||
|
version: 1,
|
||||||
|
subtitle: '={{$parameter["event"]}}',
|
||||||
|
description: 'Handle WhatsApp events via webhooks',
|
||||||
|
defaults: {
|
||||||
|
name: 'WhatsApp Trigger',
|
||||||
|
},
|
||||||
|
inputs: [],
|
||||||
|
outputs: [NodeConnectionType.Main],
|
||||||
|
credentials: [
|
||||||
|
{
|
||||||
|
name: 'whatsAppTriggerApi',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
webhooks: [
|
||||||
|
{
|
||||||
|
name: 'setup',
|
||||||
|
httpMethod: 'GET',
|
||||||
|
responseMode: 'onReceived',
|
||||||
|
path: 'webhook',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'default',
|
||||||
|
httpMethod: 'POST',
|
||||||
|
responseMode: 'onReceived',
|
||||||
|
path: 'webhook',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
displayName:
|
||||||
|
'Due to Facebook API limitations, you can use just one WhatsApp trigger for each Facebook App',
|
||||||
|
name: 'whatsAppNotice',
|
||||||
|
type: 'notice',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Trigger On',
|
displayName: 'Trigger On',
|
||||||
name: 'updates',
|
name: 'updates',
|
||||||
@@ -68,51 +133,50 @@ const whatsappTriggerDescription: INodeProperties[] = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
|
||||||
|
|
||||||
export class WhatsAppTrigger implements INodeType {
|
|
||||||
description: INodeTypeDescription = {
|
|
||||||
displayName: 'WhatsApp Trigger',
|
|
||||||
name: 'whatsAppTrigger',
|
|
||||||
icon: 'file:whatsapp.svg',
|
|
||||||
group: ['trigger'],
|
|
||||||
version: 1,
|
|
||||||
subtitle: '={{$parameter["event"]}}',
|
|
||||||
description: 'Handle WhatsApp events via webhooks',
|
|
||||||
defaults: {
|
|
||||||
name: 'WhatsApp Trigger',
|
|
||||||
},
|
|
||||||
inputs: [],
|
|
||||||
outputs: [NodeConnectionType.Main],
|
|
||||||
credentials: [
|
|
||||||
{
|
{
|
||||||
name: 'whatsAppTriggerApi',
|
displayName: 'Options',
|
||||||
required: true,
|
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',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
webhooks: [
|
|
||||||
{
|
|
||||||
name: 'setup',
|
|
||||||
httpMethod: 'GET',
|
|
||||||
responseMode: 'onReceived',
|
|
||||||
path: 'webhook',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'default',
|
|
||||||
httpMethod: 'POST',
|
|
||||||
responseMode: 'onReceived',
|
|
||||||
path: 'webhook',
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
properties: [
|
|
||||||
{
|
|
||||||
displayName:
|
|
||||||
'Due to Facebook API limitations, you can use just one WhatsApp trigger for each Facebook App',
|
|
||||||
name: 'whatsAppNotice',
|
|
||||||
type: 'notice',
|
|
||||||
default: '',
|
|
||||||
},
|
},
|
||||||
...whatsappTriggerDescription,
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -228,12 +292,14 @@ export class WhatsAppTrigger implements INodeType {
|
|||||||
.map((change) => ({ ...change.value, field: change.field })),
|
.map((change) => ({ ...change.value, field: change.field })),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (events.length === 0) {
|
const options = this.getNodeParameter('options', {}) as { messageStatusUpdates?: string[] };
|
||||||
return {};
|
|
||||||
}
|
const returnData = filterStatuses(events, options.messageStatusUpdates);
|
||||||
|
|
||||||
|
if (returnData.length === 0) return {};
|
||||||
|
|
||||||
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 BaseFacebookResponse<TData> = { data: TData };
|
||||||
export type BasePaginatedFacebookResponse<TData> = BaseFacebookResponse<TData> & {
|
export type BasePaginatedFacebookResponse<TData> = BaseFacebookResponse<TData> & {
|
||||||
@@ -70,15 +70,15 @@ export interface WhatsAppPageEvent {
|
|||||||
entry: WhatsAppEventEntry[];
|
entry: WhatsAppEventEntry[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type WhatsAppEventChanges = Array<{
|
||||||
|
field: string;
|
||||||
|
value: { statuses?: Array<{ status: string }> };
|
||||||
|
}>;
|
||||||
|
|
||||||
export interface WhatsAppEventEntry {
|
export interface WhatsAppEventEntry {
|
||||||
id: string;
|
id: string;
|
||||||
time: number;
|
time: number;
|
||||||
changes: [
|
changes: WhatsAppEventChanges;
|
||||||
{
|
|
||||||
field: string;
|
|
||||||
value: IDataObject;
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FacebookFormLeadData {
|
export interface FacebookFormLeadData {
|
||||||
|
|||||||
Reference in New Issue
Block a user