mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-14 08:34:07 -08:00
7ce7285f7a
* Changes to types so that credentials can be always loaded from DB This first commit changes all return types from the execute functions and calls to get credentials to be async so we can use await. This is a first step as previously credentials were loaded in memory and always available. We will now be loading them from the DB which requires turning the whole call chain async. * Fix updated files * Removed unnecessary credential loading to improve performance * Fix typo * ⚡ Fix issue * Updated new nodes to load credentials async * ⚡ Remove not needed comment Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
443 lines
11 KiB
TypeScript
443 lines
11 KiB
TypeScript
import {
|
|
BINARY_ENCODING,
|
|
IExecuteFunctions,
|
|
} from 'n8n-core';
|
|
import {
|
|
IBinaryData,
|
|
IDataObject,
|
|
INodeExecutionData,
|
|
INodeType,
|
|
INodeTypeDescription,
|
|
NodeApiError,
|
|
NodeOperationError,
|
|
} from 'n8n-workflow';
|
|
|
|
import {
|
|
OptionsWithUri,
|
|
} from 'request';
|
|
|
|
export class FacebookGraphApi implements INodeType {
|
|
description: INodeTypeDescription = {
|
|
displayName: 'Facebook Graph API',
|
|
name: 'facebookGraphApi',
|
|
icon: 'file:facebook.svg',
|
|
group: ['transform'],
|
|
version: 1,
|
|
description: 'Interacts with Facebook using the Graph API',
|
|
defaults: {
|
|
name: 'Facebook Graph API',
|
|
color: '#3B5998',
|
|
},
|
|
inputs: ['main'],
|
|
outputs: ['main'],
|
|
credentials: [
|
|
{
|
|
name: 'facebookGraphApi',
|
|
required: true,
|
|
},
|
|
],
|
|
properties: [
|
|
{
|
|
displayName: 'Host URL',
|
|
name: 'hostUrl',
|
|
type: 'options',
|
|
options: [
|
|
{
|
|
name: 'Default',
|
|
value: 'graph.facebook.com',
|
|
},
|
|
{
|
|
name: 'Video Uploads',
|
|
value: 'graph-video.facebook.com',
|
|
},
|
|
],
|
|
default: 'graph.facebook.com',
|
|
description: 'The Host URL of the request. Almost all requests are passed to the graph.facebook.com host URL. The single exception is video uploads, which use graph-video.facebook.com.',
|
|
required: true,
|
|
},
|
|
{
|
|
displayName: 'HTTP Request Method',
|
|
name: 'httpRequestMethod',
|
|
type: 'options',
|
|
options: [
|
|
{
|
|
name: 'GET',
|
|
value: 'GET',
|
|
},
|
|
{
|
|
name: 'POST',
|
|
value: 'POST',
|
|
},
|
|
{
|
|
name: 'DELETE',
|
|
value: 'DELETE',
|
|
},
|
|
],
|
|
default: 'GET',
|
|
description: 'The HTTP Method to be used for the request.',
|
|
required: true,
|
|
},
|
|
{
|
|
displayName: 'Graph API Version',
|
|
name: 'graphApiVersion',
|
|
type: 'options',
|
|
options: [
|
|
{
|
|
name: 'Default',
|
|
value: '',
|
|
},
|
|
{
|
|
name: 'v10.0',
|
|
value: 'v10.0',
|
|
},
|
|
{
|
|
name: 'v9.0',
|
|
value: 'v9.0',
|
|
},
|
|
{
|
|
name: 'v8.0',
|
|
value: 'v8.0',
|
|
},
|
|
{
|
|
name: 'v7.0',
|
|
value: 'v7.0',
|
|
},
|
|
{
|
|
name: 'v6.0',
|
|
value: 'v6.0',
|
|
},
|
|
{
|
|
name: 'v5.0',
|
|
value: 'v5.0',
|
|
},
|
|
{
|
|
name: 'v4.0',
|
|
value: 'v4.0',
|
|
},
|
|
{
|
|
name: 'v3.3',
|
|
value: 'v3.3',
|
|
},
|
|
{
|
|
name: 'v3.2',
|
|
value: 'v3.2',
|
|
},
|
|
{
|
|
name: 'v3.1',
|
|
value: 'v3.1',
|
|
},
|
|
{
|
|
name: 'v3.0',
|
|
value: 'v3.0',
|
|
},
|
|
],
|
|
default: '',
|
|
description: 'The version of the Graph API to be used in the request.',
|
|
required: true,
|
|
},
|
|
{
|
|
displayName: 'Node',
|
|
name: 'node',
|
|
type: 'string',
|
|
default: '',
|
|
description: 'The node on which to operate. A node is an individual object with a unique ID. For example, there are many User node objects, each with a unique ID representing a person on Facebook.',
|
|
placeholder: 'me',
|
|
required: true,
|
|
},
|
|
{
|
|
displayName: 'Edge',
|
|
name: 'edge',
|
|
type: 'string',
|
|
default: '',
|
|
description: 'Edge of the node on which to operate. Edges represent collections of objects which are attached to the node.',
|
|
placeholder: 'videos',
|
|
required: false,
|
|
},
|
|
{
|
|
displayName: 'Ignore SSL Issues',
|
|
name: 'allowUnauthorizedCerts',
|
|
type: 'boolean',
|
|
default: false,
|
|
description: 'Still download the response even if SSL certificate validation is not possible. (Not recommended)',
|
|
},
|
|
{
|
|
displayName: 'Send Binary Data',
|
|
name: 'sendBinaryData',
|
|
type: 'boolean',
|
|
displayOptions: {
|
|
show: {
|
|
httpRequestMethod: [
|
|
'POST',
|
|
'PUT',
|
|
],
|
|
},
|
|
},
|
|
default: false,
|
|
required: true,
|
|
description: 'If binary data should be send as body.',
|
|
},
|
|
{
|
|
displayName: 'Binary Property',
|
|
name: 'binaryPropertyName',
|
|
type: 'string',
|
|
required: false,
|
|
default: '',
|
|
placeholder: 'file:data',
|
|
displayOptions: {
|
|
hide: {
|
|
sendBinaryData: [
|
|
false,
|
|
],
|
|
},
|
|
show: {
|
|
httpRequestMethod: [
|
|
'POST',
|
|
'PUT',
|
|
],
|
|
},
|
|
},
|
|
description: `Name of the binary property which contains the data for the file to be uploaded.<br />
|
|
For Form-Data Multipart, multiple can be provided in the format:<br />
|
|
"sendKey1:binaryProperty1,sendKey2:binaryProperty2`,
|
|
},
|
|
{
|
|
displayName: 'Options',
|
|
name: 'options',
|
|
type: 'collection',
|
|
placeholder: 'Add Option',
|
|
default: {},
|
|
options: [
|
|
{
|
|
displayName: 'Fields',
|
|
name: 'fields',
|
|
placeholder: 'Add Field',
|
|
type: 'fixedCollection',
|
|
typeOptions: {
|
|
multipleValues: true,
|
|
},
|
|
displayOptions: {
|
|
show: {
|
|
'/httpRequestMethod': [
|
|
'GET',
|
|
],
|
|
},
|
|
},
|
|
description: 'The list of fields to request in the GET request.',
|
|
default: {},
|
|
options: [
|
|
{
|
|
name: 'field',
|
|
displayName: 'Field',
|
|
values: [
|
|
{
|
|
displayName: 'Name',
|
|
name: 'name',
|
|
type: 'string',
|
|
default: '',
|
|
description: 'Name of the field.',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
displayName: 'Query Parameters',
|
|
name: 'queryParameters',
|
|
placeholder: 'Add Parameter',
|
|
type: 'fixedCollection',
|
|
typeOptions: {
|
|
multipleValues: true,
|
|
},
|
|
description: 'The query parameters to send',
|
|
default: {},
|
|
options: [
|
|
{
|
|
name: 'parameter',
|
|
displayName: 'Parameter',
|
|
values: [
|
|
{
|
|
displayName: 'Name',
|
|
name: 'name',
|
|
type: 'string',
|
|
default: '',
|
|
description: 'Name of the parameter.',
|
|
},
|
|
{
|
|
displayName: 'Value',
|
|
name: 'value',
|
|
type: 'string',
|
|
default: '',
|
|
description: 'Value of the parameter.',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
displayName: 'Query Parameters JSON',
|
|
name: 'queryParametersJson',
|
|
type: 'json',
|
|
default: '{}',
|
|
placeholder: '{\"field_name\": \"field_value\"}',
|
|
description: 'The query parameters to send, defined as a JSON object',
|
|
required: false,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
|
|
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
|
const items = this.getInputData();
|
|
|
|
let response: any; // tslint:disable-line:no-any
|
|
const returnItems: INodeExecutionData[] = [];
|
|
|
|
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
|
const graphApiCredentials = await this.getCredentials('facebookGraphApi');
|
|
|
|
const hostUrl = this.getNodeParameter('hostUrl', itemIndex) as string;
|
|
const httpRequestMethod = this.getNodeParameter('httpRequestMethod', itemIndex) as string;
|
|
let graphApiVersion = this.getNodeParameter('graphApiVersion', itemIndex) as string;
|
|
const node = this.getNodeParameter('node', itemIndex) as string;
|
|
const edge = this.getNodeParameter('edge', itemIndex) as string;
|
|
const options = this.getNodeParameter('options', itemIndex, {}) as IDataObject;
|
|
|
|
if (graphApiVersion !== '') {
|
|
graphApiVersion += '/';
|
|
}
|
|
|
|
let uri = `https://${hostUrl}/${graphApiVersion}${node}`;
|
|
if (edge) {
|
|
uri = `${uri}/${edge}`;
|
|
}
|
|
|
|
const requestOptions : OptionsWithUri = {
|
|
headers: {
|
|
accept: 'application/json,text/*;q=0.99',
|
|
},
|
|
method: httpRequestMethod,
|
|
uri,
|
|
json: true,
|
|
gzip: true,
|
|
qs: {
|
|
access_token: graphApiCredentials!.accessToken,
|
|
},
|
|
rejectUnauthorized: !this.getNodeParameter('allowUnauthorizedCerts', itemIndex, false) as boolean,
|
|
};
|
|
|
|
if (options !== undefined) {
|
|
// Build fields query parameter as a comma separated list
|
|
if (options.fields !== undefined) {
|
|
const fields = options.fields as IDataObject;
|
|
if (fields.field !== undefined) {
|
|
const fieldsCsv = (fields.field as IDataObject[]).map(field => field.name).join(',');
|
|
requestOptions.qs.fields = fieldsCsv;
|
|
}
|
|
}
|
|
|
|
// Add the query parameters defined in the UI
|
|
if (options.queryParameters !== undefined) {
|
|
const queryParameters = options.queryParameters as IDataObject;
|
|
|
|
if (queryParameters.parameter !== undefined) {
|
|
for (const queryParameter of queryParameters.parameter as IDataObject[]) {
|
|
requestOptions.qs[queryParameter.name as string] = queryParameter.value;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add the query parameters defined as a JSON object
|
|
if (options.queryParametersJson) {
|
|
let queryParametersJsonObj = {};
|
|
try
|
|
{
|
|
queryParametersJsonObj = JSON.parse(options.queryParametersJson as string);
|
|
} catch { /* Do nothing, at least for now */}
|
|
const qs = requestOptions.qs;
|
|
requestOptions.qs = {
|
|
...qs,
|
|
...queryParametersJsonObj,
|
|
};
|
|
}
|
|
}
|
|
|
|
const sendBinaryData = this.getNodeParameter('sendBinaryData', itemIndex, false) as boolean;
|
|
if (sendBinaryData) {
|
|
const item = items[itemIndex];
|
|
if (item.binary === undefined) {
|
|
throw new NodeOperationError(this.getNode(), 'No binary data exists on item!');
|
|
}
|
|
|
|
const binaryPropertyNameFull = this.getNodeParameter('binaryPropertyName', itemIndex) as string;
|
|
|
|
let propertyName = 'file';
|
|
let binaryPropertyName = binaryPropertyNameFull;
|
|
if (binaryPropertyNameFull.includes(':')) {
|
|
const binaryPropertyNameParts = binaryPropertyNameFull.split(':');
|
|
propertyName = binaryPropertyNameParts[0];
|
|
binaryPropertyName = binaryPropertyNameParts[1];
|
|
}
|
|
|
|
if (item.binary[binaryPropertyName] === undefined) {
|
|
throw new NodeOperationError(this.getNode(), `No binary data property "${binaryPropertyName}" does not exists on item!`);
|
|
}
|
|
|
|
const binaryProperty = item.binary[binaryPropertyName] as IBinaryData;
|
|
|
|
requestOptions.formData = {
|
|
[propertyName]: {
|
|
value: Buffer.from(binaryProperty.data, BINARY_ENCODING),
|
|
options: {
|
|
filename: binaryProperty.fileName,
|
|
contentType: binaryProperty.mimeType,
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
try {
|
|
// Now that the options are all set make the actual http request
|
|
response = await this.helpers.request(requestOptions);
|
|
} catch (error) {
|
|
if (this.continueOnFail() === false) {
|
|
throw new NodeApiError(this.getNode(), error);
|
|
}
|
|
|
|
let errorItem;
|
|
if (error.response !== undefined) {
|
|
// Since this is a Graph API node and we already know the request was
|
|
// not successful, we'll go straight to the error details.
|
|
const graphApiErrors = error.response.body?.error ?? {};
|
|
|
|
errorItem = {
|
|
statusCode: error.statusCode,
|
|
...graphApiErrors,
|
|
headers: error.response.headers,
|
|
};
|
|
} else {
|
|
// Unknown Graph API response, we'll dump everything in the response item
|
|
errorItem = error;
|
|
}
|
|
returnItems.push({ json: { ...errorItem } });
|
|
|
|
continue;
|
|
}
|
|
|
|
if (typeof response === 'string') {
|
|
if (this.continueOnFail() === false) {
|
|
throw new NodeOperationError(this.getNode(), 'Response body is not valid JSON.');
|
|
}
|
|
|
|
returnItems.push({ json: { message: response } });
|
|
continue;
|
|
}
|
|
|
|
returnItems.push({json: response});
|
|
}
|
|
|
|
return [returnItems];
|
|
}
|
|
}
|