Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Facebook Lead Ads Trigger Node): Add Facebook Lead Ads Trigger Node #7113

Merged
merged 14 commits into from Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 16 additions & 0 deletions packages/core/bin/generate-ui-types
Expand Up @@ -29,6 +29,21 @@ function findReferencedMethods(obj, refs = {}, latestName = '') {
return refs;
}

function addWebhookLifecycle(nodeType) {
if (nodeType.description.webhooks) {
nodeType.description.webhooks = nodeType.description.webhooks.map((webhook) => {
const webhookMethods =
nodeType?.webhookMethods?.[webhook.name] ?? nodeType?.webhookMethods?.default;
webhook.hasLifecycleMethods = Boolean(
webhookMethods?.checkExists && webhookMethods?.create && webhookMethods?.delete,
);
return webhook;
});
}

return nodeType;
}

(async () => {
const loader = new PackageDirectoryLoader(packageDir);
await loader.loadAll();
Expand Down Expand Up @@ -60,6 +75,7 @@ function findReferencedMethods(obj, refs = {}, latestName = '') {
.map((data) => {
const nodeType = NodeHelpers.getVersionedNodeType(data.type);
NodeHelpers.applySpecialNodeParameters(nodeType);
addWebhookLifecycle(nodeType);
return data.type;
})
.flatMap((nodeData) => {
Expand Down
6 changes: 3 additions & 3 deletions packages/editor-ui/src/components/NodeWebhooks.vue
Expand Up @@ -61,12 +61,12 @@
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import type { INodeTypeDescription, IWebhookDescription } from 'n8n-workflow';
import { defineComponent } from 'vue';

import { useToast } from '@/composables';
import { FORM_TRIGGER_NODE_TYPE, OPEN_URL_PANEL_TRIGGER_NODE_TYPES } from '@/constants';
import { copyPaste } from '@/mixins/copyPaste';
import { useToast } from '@/composables';
import { workflowHelpers } from '@/mixins/workflowHelpers';

export default defineComponent({
Expand Down Expand Up @@ -94,7 +94,7 @@ export default defineComponent({
}

return (this.nodeType as INodeTypeDescription).webhooks!.filter(
(webhookData) => webhookData.restartWebhook !== true,
(webhookData) => webhookData.restartWebhook !== true && !webhookData.hasLifecycleMethods,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@elsmr just FYI: we've reverted this check in #7783

For more context: Slack thread.

);
},
baseText() {
Expand Down
@@ -0,0 +1,52 @@
import type { ICredentialType, INodeProperties } from 'n8n-workflow';

export class FacebookLeadAdsOAuth2Api implements ICredentialType {
name = 'facebookLeadAdsOAuth2Api';

extends = ['oAuth2Api'];

displayName = 'Facebook Lead Ads OAuth2 API';

documentationUrl = 'facebookleadads';

properties: INodeProperties[] = [
{
displayName: 'Grant Type',
name: 'grantType',
type: 'hidden',
default: 'authorizationCode',
},
{
displayName: 'Authorization URL',
name: 'authUrl',
type: 'hidden',
default: 'https://www.facebook.com/v17.0/dialog/oauth',
required: true,
},
{
displayName: 'Access Token URL',
name: 'accessTokenUrl',
type: 'hidden',
default: 'https://graph.facebook.com/v17.0/oauth/access_token',
required: true,
},
{
displayName: 'Scope',
name: 'scope',
type: 'hidden',
default: 'leads_retrieval pages_show_list pages_manage_metadata pages_manage_ads',
},
{
displayName: 'Auth URI Query Parameters',
name: 'authQueryParameters',
type: 'hidden',
default: '',
},
{
displayName: 'Authentication',
name: 'authentication',
type: 'hidden',
default: 'header',
},
];
}
38 changes: 24 additions & 14 deletions packages/nodes-base/nodes/Facebook/FacebookTrigger.node.ts
@@ -1,23 +1,24 @@
import { createHmac } from 'crypto';
import type {
IHookFunctions,
IWebhookFunctions,
IDataObject,
IHookFunctions,
ILoadOptionsFunctions,
INodePropertyOptions,
INodeType,
INodeTypeDescription,
IWebhookFunctions,
IWebhookResponseData,
JsonObject,
} from 'n8n-workflow';
import { NodeApiError } from 'n8n-workflow';
import { NodeApiError, NodeOperationError } from 'n8n-workflow';

import { v4 as uuid } from 'uuid';

import { snakeCase } from 'change-case';

import { facebookApiRequest, getAllFields, getFields } from './GenericFunctions';

import { createHmac } from 'crypto';
import type { FacebookWebhookSubscription } from './types';

export class FacebookTrigger implements INodeType {
description: INodeTypeDescription = {
Expand Down Expand Up @@ -177,18 +178,27 @@ export class FacebookTrigger implements INodeType {
const object = this.getNodeParameter('object') as string;
const appId = this.getNodeParameter('appId') as string;

const { data } = await facebookApiRequest.call(this, 'GET', `/${appId}/subscriptions`, {});
const { data } = (await facebookApiRequest.call(
this,
'GET',
`/${appId}/subscriptions`,
{},
)) as { data: FacebookWebhookSubscription[] };

const subscription = data.find((webhook) => webhook.object === object && webhook.status);

for (const webhook of data) {
if (
webhook.target === webhookUrl &&
webhook.object === object &&
webhook.status === true
) {
return true;
}
if (!subscription) {
return false;
}
return false;

if (subscription.callback_url !== webhookUrl) {
throw new NodeOperationError(
this.getNode(),
`The Facebook App ID ${appId} already has a webhook subscription. Delete it or use another App before executing the trigger. Due to Facebook API limitations, you can have just one trigger per App.`,
);
}

return true;
},
async create(this: IHookFunctions): Promise<boolean> {
const webhookData = this.getWorkflowStaticData('node');
Expand Down
29 changes: 29 additions & 0 deletions packages/nodes-base/nodes/Facebook/types.ts
@@ -0,0 +1,29 @@
export interface FacebookEvent {
object: string;
entry: FacebookPageEventEntry[];
}

export interface FacebookPageEventEntry {
id: string;
time: number;
changes: [
{
field: 'leadgen';
value: {
ad_id: string;
form_id: string;
leadgen_id: string;
created_time: number;
page_id: string;
adgroup_id: string;
};
},
];
}

export interface FacebookWebhookSubscription {
object: string;
callback_url: string;
fields: string[];
status: boolean;
}
@@ -0,0 +1,18 @@
{
"node": "n8n-nodes-base.facebookLeadAdsTrigger",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": ["Marketing & Content"],
"resources": {
"credentialDocumentation": [
{
"url": "https://docs.n8n.io/integrations/credentials/facebookleadads/"
}
],
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/trigger-nodes/n8n-nodes-base.facebookleadadstrigger/"
}
]
}
}