2023-10-20 04:43:55 -07:00
import { createHmac } from 'crypto' ;
2023-01-27 03:22:44 -08:00
import type {
2020-11-04 03:25:18 -08:00
IDataObject ,
2023-10-20 04:43:55 -07:00
IHookFunctions ,
2021-07-29 06:32:01 -07:00
ILoadOptionsFunctions ,
INodePropertyOptions ,
2020-11-04 03:25:18 -08:00
INodeType ,
INodeTypeDescription ,
2023-10-20 04:43:55 -07:00
IWebhookFunctions ,
2020-11-04 03:25:18 -08:00
IWebhookResponseData ,
2023-02-27 19:39:43 -08:00
JsonObject ,
2020-11-04 03:25:18 -08:00
} from 'n8n-workflow' ;
2023-10-20 04:43:55 -07:00
import { NodeApiError , NodeOperationError } from 'n8n-workflow' ;
2020-11-04 03:25:18 -08:00
2021-06-13 10:17:39 -07:00
import { v4 as uuid } from 'uuid' ;
2020-11-04 03:25:18 -08:00
2022-08-01 13:47:55 -07:00
import { snakeCase } from 'change-case' ;
2020-11-04 03:25:18 -08:00
2022-08-01 13:47:55 -07:00
import { facebookApiRequest , getAllFields , getFields } from './GenericFunctions' ;
2020-11-04 03:25:18 -08:00
2023-10-20 04:43:55 -07:00
import type { FacebookWebhookSubscription } from './types' ;
2020-11-04 03:25:18 -08:00
export class FacebookTrigger implements INodeType {
description : INodeTypeDescription = {
displayName : 'Facebook Trigger' ,
name : 'facebookTrigger' ,
2021-04-30 12:22:46 -07:00
icon : 'file:facebook.svg' ,
2020-11-04 03:25:18 -08:00
group : [ 'trigger' ] ,
version : 1 ,
subtitle : '={{$parameter["appId"] +"/"+ $parameter["object"]}}' ,
2021-07-03 05:40:16 -07:00
description : 'Starts the workflow when Facebook events occur' ,
2020-11-04 03:25:18 -08:00
defaults : {
name : 'Facebook Trigger' ,
} ,
inputs : [ ] ,
outputs : [ 'main' ] ,
credentials : [
{
2020-11-04 03:26:09 -08:00
name : 'facebookGraphAppApi' ,
2020-11-04 03:25:18 -08:00
required : true ,
} ,
] ,
webhooks : [
{
name : 'setup' ,
httpMethod : 'GET' ,
responseMode : 'onReceived' ,
path : 'webhook' ,
} ,
{
name : 'default' ,
httpMethod : 'POST' ,
responseMode : 'onReceived' ,
path : 'webhook' ,
} ,
] ,
properties : [
2021-07-29 06:32:01 -07:00
{
displayName : 'APP ID' ,
name : 'appId' ,
type : 'string' ,
required : true ,
default : '' ,
description : 'Facebook APP ID' ,
} ,
2020-11-04 03:25:18 -08:00
{
displayName : 'Object' ,
name : 'object' ,
type : 'options' ,
options : [
{
name : 'Ad Account' ,
value : 'adAccount' ,
description : 'Get updates about Ad Account' ,
} ,
{
name : 'Application' ,
value : 'application' ,
description : 'Get updates about the app' ,
} ,
{
name : 'Certificate Transparency' ,
value : 'certificateTransparency' ,
description : 'Get updates about Certificate Transparency' ,
} ,
{
name : 'Group' ,
value : 'group' ,
description : 'Get updates about activity in groups and events in groups for Workplace' ,
} ,
{
name : 'Instagram' ,
value : 'instagram' ,
description : 'Get updates about comments on your media' ,
} ,
{
name : 'Link' ,
value : 'link' ,
description : 'Get updates about links for rich previews by an external provider' ,
} ,
{
name : 'Page' ,
value : 'page' ,
description : 'Page updates' ,
} ,
{
name : 'Permissions' ,
value : 'permissions' ,
description : 'Updates regarding granting or revoking permissions' ,
} ,
{
name : 'User' ,
value : 'user' ,
description : 'User profile updates' ,
} ,
{
name : 'Whatsapp Business Account' ,
value : 'whatsappBusinessAccount' ,
description : 'Get updates about Whatsapp business account' ,
} ,
{
name : 'Workplace Security' ,
value : 'workplaceSecurity' ,
description : 'Get updates about Workplace Security' ,
} ,
] ,
required : true ,
default : 'user' ,
description : 'The object to subscribe to' ,
} ,
2021-07-29 06:32:01 -07:00
//https://developers.facebook.com/docs/graph-api/webhooks/reference/page
2020-11-04 03:25:18 -08:00
{
2022-06-20 07:54:01 -07:00
displayName : 'Field Names or IDs' ,
2021-07-29 06:32:01 -07:00
name : 'fields' ,
type : 'multiOptions' ,
typeOptions : {
loadOptionsMethod : 'getObjectFields' ,
2022-08-01 13:47:55 -07:00
loadOptionsDependsOn : [ 'object' ] ,
2021-07-29 06:32:01 -07:00
} ,
default : [ ] ,
2022-08-01 13:47:55 -07:00
description :
'The set of fields in this object that are subscribed to. Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.' ,
2020-11-04 03:25:18 -08:00
} ,
{
displayName : 'Options' ,
name : 'options' ,
type : 'collection' ,
default : { } ,
placeholder : 'Add option' ,
options : [
{
2022-06-03 10:23:49 -07:00
displayName : 'Include Values' ,
2020-11-04 03:25:18 -08:00
name : 'includeValues' ,
type : 'boolean' ,
default : true ,
2022-06-20 07:54:01 -07:00
description : 'Whether change notifications should include the new values' ,
2020-11-04 03:25:18 -08:00
} ,
] ,
} ,
] ,
} ;
2021-07-29 06:32:01 -07:00
methods = {
loadOptions : {
2023-04-19 07:00:49 -07:00
// Get all the available organizations to display them to user so that they can
2021-07-29 06:32:01 -07:00
// select them easily
async getObjectFields ( this : ILoadOptionsFunctions ) : Promise < INodePropertyOptions [ ] > {
const object = this . getCurrentNodeParameter ( 'object' ) as string ;
return getFields ( object ) as INodePropertyOptions [ ] ;
} ,
} ,
} ;
2020-11-04 03:25:18 -08:00
webhookMethods = {
default : {
async checkExists ( this : IHookFunctions ) : Promise < boolean > {
const webhookUrl = this . getNodeWebhookUrl ( 'default' ) as string ;
const object = this . getNodeParameter ( 'object' ) as string ;
const appId = this . getNodeParameter ( 'appId' ) as string ;
2023-10-20 04:43:55 -07:00
const { data } = ( await facebookApiRequest . call (
this ,
'GET' ,
` / ${ appId } /subscriptions ` ,
{ } ,
) ) as { data : FacebookWebhookSubscription [ ] } ;
const subscription = data . find ( ( webhook ) = > webhook . object === object && webhook . status ) ;
2020-11-04 03:25:18 -08:00
2023-10-20 04:43:55 -07:00
if ( ! subscription ) {
return false ;
2020-11-04 03:25:18 -08:00
}
2023-10-20 04:43:55 -07:00
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. ` ,
2023-12-07 07:57:02 -08:00
{ level : 'warning' } ,
2023-10-20 04:43:55 -07:00
) ;
}
return true ;
2020-11-04 03:25:18 -08:00
} ,
async create ( this : IHookFunctions ) : Promise < boolean > {
const webhookData = this . getWorkflowStaticData ( 'node' ) ;
const webhookUrl = this . getNodeWebhookUrl ( 'default' ) as string ;
const object = this . getNodeParameter ( 'object' ) as string ;
const appId = this . getNodeParameter ( 'appId' ) as string ;
2021-07-29 06:32:01 -07:00
const fields = this . getNodeParameter ( 'fields' ) as string [ ] ;
2020-11-04 03:25:18 -08:00
const options = this . getNodeParameter ( 'options' ) as IDataObject ;
const body = {
object : snakeCase ( object ) ,
callback_url : webhookUrl ,
verify_token : uuid ( ) ,
2022-08-01 13:47:55 -07:00
fields : fields.includes ( '*' ) ? getAllFields ( object ) : fields ,
2020-11-04 03:25:18 -08:00
} as IDataObject ;
if ( options . includeValues !== undefined ) {
body . include_values = options . includeValues ;
}
2022-08-01 13:47:55 -07:00
const responseData = await facebookApiRequest . call (
this ,
'POST' ,
` / ${ appId } /subscriptions ` ,
body ,
) ;
2020-11-04 03:25:18 -08:00
webhookData . verifyToken = body . verify_token ;
if ( responseData . success !== true ) {
// Facebook did not return success, so something went wrong
2023-02-27 19:39:43 -08:00
throw new NodeApiError ( this . getNode ( ) , responseData as JsonObject , {
2022-08-01 13:47:55 -07:00
message : 'Facebook webhook creation response did not contain the expected data.' ,
} ) ;
2020-11-04 03:25:18 -08:00
}
return true ;
} ,
async delete ( this : IHookFunctions ) : Promise < boolean > {
const appId = this . getNodeParameter ( 'appId' ) as string ;
const object = this . getNodeParameter ( 'object' ) as string ;
try {
2022-08-01 13:47:55 -07:00
await facebookApiRequest . call ( this , 'DELETE' , ` / ${ appId } /subscriptions ` , {
object : snakeCase ( object ) ,
} ) ;
2021-04-16 09:33:36 -07:00
} catch ( error ) {
2020-11-04 03:25:18 -08:00
return false ;
}
return true ;
} ,
} ,
} ;
async webhook ( this : IWebhookFunctions ) : Promise < IWebhookResponseData > {
2022-12-02 12:54:28 -08:00
const bodyData = this . getBodyData ( ) ;
2020-11-04 03:25:18 -08:00
const query = this . getQueryData ( ) as IDataObject ;
const res = this . getResponseObject ( ) ;
const req = this . getRequestObject ( ) ;
const headerData = this . getHeaderData ( ) as IDataObject ;
2022-04-14 23:00:47 -07:00
const credentials = await this . getCredentials ( 'facebookGraphAppApi' ) ;
2020-11-04 03:25:18 -08:00
// Check if we're getting facebook's challenge request (https://developers.facebook.com/docs/graph-api/webhooks/getting-started)
if ( this . getWebhookName ( ) === 'setup' ) {
if ( query [ 'hub.challenge' ] ) {
//TODO
//compare hub.verify_token with the saved token
//const webhookData = this.getWorkflowStaticData('node');
// if (webhookData.verifyToken !== query['hub.verify_token']) {
// return {};
// }
res . status ( 200 ) . send ( query [ 'hub.challenge' ] ) . end ( ) ;
return {
noWebhookResponse : true ,
} ;
}
}
// validate signature if app secret is set
if ( credentials . appSecret !== '' ) {
2022-08-01 13:47:55 -07:00
const computedSignature = createHmac ( 'sha1' , credentials . appSecret as string )
. update ( req . rawBody )
. digest ( 'hex' ) ;
2020-11-04 03:25:18 -08:00
if ( headerData [ 'x-hub-signature' ] !== ` sha1= ${ computedSignature } ` ) {
return { } ;
}
}
return {
2022-08-01 13:47:55 -07:00
workflowData : [ this . helpers . returnJsonArray ( bodyData . entry as IDataObject [ ] ) ] ,
2020-11-04 03:25:18 -08:00
} ;
}
}