replace custom HTTP client with n8n's requestWithAuthentication helper (#2635)
This commit is contained in:
committed by
GitHub
parent
9fd8680c83
commit
f121ce0ce6
@@ -1,77 +1,46 @@
|
||||
import { FieldType, IDataObject, IExecuteSingleFunctions, IHttpRequestMethods, IHttpRequestOptions, ILoadOptionsFunctions, INodePropertyOptions, INodeType, INodeTypeDescription, NodeConnectionType, ResourceMapperField, ResourceMapperFields } from 'n8n-workflow';
|
||||
import https from 'https';
|
||||
import http from 'http';
|
||||
import { URL } from 'url';
|
||||
import {
|
||||
FieldType,
|
||||
IDataObject,
|
||||
IExecuteSingleFunctions,
|
||||
IHttpRequestMethods,
|
||||
IHttpRequestOptions,
|
||||
ILoadOptionsFunctions,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
ResourceMapperField,
|
||||
ResourceMapperFields,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
async function makeRequest(url: string, options: any = {}): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const parsedUrl = new URL(url);
|
||||
const transport = parsedUrl.protocol === 'https:' ? https : http;
|
||||
const requestOptions = {
|
||||
hostname: parsedUrl.hostname,
|
||||
path: parsedUrl.pathname + parsedUrl.search,
|
||||
port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80),
|
||||
method: options.method || 'GET',
|
||||
headers: options.headers || {},
|
||||
};
|
||||
|
||||
const req = transport.request(requestOptions, (res) => {
|
||||
let data = '';
|
||||
|
||||
res.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
||||
const response = {
|
||||
ok: true,
|
||||
status: res.statusCode,
|
||||
statusText: res.statusMessage || '',
|
||||
headers: res.headers,
|
||||
json: () => {
|
||||
try {
|
||||
return Promise.resolve(JSON.parse(data));
|
||||
} catch (e) {
|
||||
return Promise.reject(new Error('Invalid JSON response'));
|
||||
}
|
||||
},
|
||||
text: () => Promise.resolve(data),
|
||||
blob: () => Promise.resolve(new Blob([data])),
|
||||
arrayBuffer: () => Promise.resolve(Buffer.from(data)),
|
||||
clone: () => response,
|
||||
};
|
||||
resolve(response);
|
||||
} else {
|
||||
reject(new Error(`Request failed with status code ${res.statusCode}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (error) => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
if (options.body) {
|
||||
req.write(options.body);
|
||||
}
|
||||
|
||||
req.end();
|
||||
});
|
||||
async function skyvernApiRequest(
|
||||
this: IExecuteSingleFunctions | ILoadOptionsFunctions,
|
||||
method: IHttpRequestMethods,
|
||||
endpoint: string,
|
||||
body: IDataObject | undefined = undefined,
|
||||
): Promise<any> {
|
||||
const credentials = await this.getCredentials('skyvernApi');
|
||||
const options: IHttpRequestOptions = {
|
||||
baseURL: credentials.baseUrl as string,
|
||||
method,
|
||||
url: endpoint,
|
||||
body,
|
||||
json: true,
|
||||
};
|
||||
return this.helpers.requestWithAuthentication.call(this, 'skyvernApi', options);
|
||||
}
|
||||
|
||||
export class Skyvern implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Skyvern',
|
||||
name: 'skyvern',
|
||||
icon: 'file:skyvern.png', // eslint-disable-line
|
||||
icon: 'file:skyvern.svg',
|
||||
group: ['transform'],
|
||||
description: 'Node to interact with Skyvern',
|
||||
defaults: {
|
||||
name: 'Skyvern',
|
||||
},
|
||||
inputs: [NodeConnectionType.Main], // eslint-disable-line
|
||||
outputs: [NodeConnectionType.Main], // eslint-disable-line
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'skyvernApi',
|
||||
@@ -98,51 +67,100 @@ export class Skyvern implements INodeType {
|
||||
},
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'taskOperation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
required: true,
|
||||
default: 'dispatch',
|
||||
default: 'dispatchTask',
|
||||
options: [
|
||||
{
|
||||
name: 'Dispatch a Task',
|
||||
value: 'dispatch',
|
||||
value: 'dispatchTask',
|
||||
action: 'Dispatch a task to execute asynchronously',
|
||||
description: 'Dispatch a task to execute asynchronously',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['task'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
request: {
|
||||
baseURL: '={{$credentials.baseUrl}}',
|
||||
method: 'POST',
|
||||
url: '/v1/run/tasks',
|
||||
},
|
||||
send: {
|
||||
preSend: [
|
||||
async function (
|
||||
this: IExecuteSingleFunctions,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<IHttpRequestOptions> {
|
||||
const taskOptions = this.getNodeParameter('taskOptions') as IDataObject;
|
||||
const legacy_engine = taskOptions['engine'] as string | null;
|
||||
if (legacy_engine === 'v1') {
|
||||
(requestOptions.body as IDataObject)['engine'] = 'skyvern-1.0';
|
||||
} else if (legacy_engine === 'v2') {
|
||||
(requestOptions.body as IDataObject)['engine'] = 'skyvern-2.0';
|
||||
}
|
||||
return requestOptions;
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Get a Task',
|
||||
value: 'get',
|
||||
value: 'getTask',
|
||||
action: 'Get a task by ID',
|
||||
description: 'Get a task by ID',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['task'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
request: {
|
||||
baseURL: '={{$credentials.baseUrl}}',
|
||||
method: 'GET',
|
||||
url: '/v1/run/tasks',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Get a Workflow Run',
|
||||
value: 'getWorkflow',
|
||||
action: 'Get a workflow run by ID',
|
||||
description: 'Get a workflow run by ID',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['workflow'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
request: {
|
||||
baseURL: '={{$credentials.baseUrl}}',
|
||||
method: 'GET',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Dispatch a Workflow Run',
|
||||
value: 'dispatchWorkflow',
|
||||
action: 'Dispatch a workflow run to execute asynchronously',
|
||||
description: 'Dispatch a workflow run to execute asynchronously',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['workflow'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
request: {
|
||||
baseURL: '={{$credentials.baseUrl}}',
|
||||
method: 'POST',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['task'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
request: {
|
||||
baseURL: '={{$credentials.baseUrl}}',
|
||||
method: '={{ $value === "dispatch" ? "POST" : "GET" }}' as IHttpRequestMethods,
|
||||
url: '={{"/v1/run/tasks"}}',
|
||||
},
|
||||
send: {
|
||||
preSend: [
|
||||
async function (this: IExecuteSingleFunctions, requestOptions: IHttpRequestOptions): Promise<IHttpRequestOptions> {
|
||||
const taskOperation = this.getNodeParameter('taskOperation');
|
||||
if (taskOperation === "get") return requestOptions;
|
||||
|
||||
const taskOptions: IDataObject = this.getNodeParameter('taskOptions') as IDataObject;
|
||||
const legacy_engine = taskOptions["engine"] as string | null
|
||||
if (legacy_engine === "v1") {
|
||||
(requestOptions.body as IDataObject)['engine'] = "skyvern-1.0";
|
||||
}else if (legacy_engine === "v2") {
|
||||
(requestOptions.body as IDataObject)['engine'] = "skyvern-2.0";
|
||||
}
|
||||
return requestOptions;
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'User Prompt',
|
||||
@@ -155,7 +173,7 @@ export class Skyvern implements INodeType {
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['task'],
|
||||
taskOperation: ['dispatch'],
|
||||
operation: ['dispatchTask'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
@@ -176,7 +194,7 @@ export class Skyvern implements INodeType {
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['task'],
|
||||
taskOperation: ['dispatch'],
|
||||
operation: ['dispatchTask'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
@@ -197,7 +215,7 @@ export class Skyvern implements INodeType {
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['task'],
|
||||
taskOperation: ['dispatch'],
|
||||
operation: ['dispatchTask'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
@@ -218,7 +236,7 @@ export class Skyvern implements INodeType {
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['task'],
|
||||
taskOperation: ['get'],
|
||||
operation: ['getTask'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
@@ -292,12 +310,12 @@ export class Skyvern implements INodeType {
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['task'],
|
||||
taskOperation: ['dispatch'],
|
||||
operation: ['dispatchTask'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Workflow Title or ID', // eslint-disable-line
|
||||
displayName: 'Workflow Name or ID',
|
||||
description: 'The title of the workflow. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
||||
name: 'workflowId',
|
||||
type: 'options',
|
||||
@@ -313,36 +331,6 @@ export class Skyvern implements INodeType {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Workflow Operation',
|
||||
name: 'workflowOperation',
|
||||
type: 'options',
|
||||
required: true,
|
||||
default: 'get',
|
||||
options: [
|
||||
{
|
||||
name: 'Get a Workflow Run',
|
||||
value: 'get',
|
||||
description: 'Get a workflow run by ID',
|
||||
},
|
||||
{
|
||||
name: 'Dispatch a Workflow Run',
|
||||
value: 'dispatch',
|
||||
description: 'Dispatch a workflow run to execute asynchronously',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['workflow'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
request: {
|
||||
baseURL: '={{$credentials.baseUrl}}',
|
||||
method: '={{ $value === "dispatch" ? "POST" : "GET" }}' as IHttpRequestMethods,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Workflow Run ID',
|
||||
description: 'The ID of the workflow run',
|
||||
@@ -353,7 +341,7 @@ export class Skyvern implements INodeType {
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['workflow'],
|
||||
workflowOperation: ['get'],
|
||||
operation: ['getWorkflow'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
@@ -376,7 +364,7 @@ export class Skyvern implements INodeType {
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['workflow'],
|
||||
workflowOperation: ['dispatch'],
|
||||
operation: ['dispatchWorkflow'],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
@@ -411,7 +399,7 @@ export class Skyvern implements INodeType {
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['workflow'],
|
||||
workflowOperation: ['dispatch'],
|
||||
operation: ['dispatchWorkflow'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
@@ -432,16 +420,12 @@ export class Skyvern implements INodeType {
|
||||
const resource = this.getCurrentNodeParameter('resource') as string;
|
||||
if (resource !== 'workflow') return [];
|
||||
|
||||
const credentials = await this.getCredentials('skyvernApi');
|
||||
const response = await makeRequest(credentials['baseUrl'] + '/api/v1/workflows?page_size=100', {
|
||||
headers: {
|
||||
'x-api-key': credentials['apiKey'],
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Request to get workflows failed'); // eslint-disable-line
|
||||
}
|
||||
const data = await response.json();
|
||||
const response = await skyvernApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
'/api/v1/workflows?page_size=100',
|
||||
);
|
||||
const data = response;
|
||||
return data.map((workflow: any) => ({
|
||||
name: workflow.title,
|
||||
value: workflow.workflow_permanent_id,
|
||||
@@ -453,22 +437,17 @@ export class Skyvern implements INodeType {
|
||||
const resource = this.getCurrentNodeParameter('resource') as string;
|
||||
if (resource !== 'workflow') return { fields: [] };
|
||||
|
||||
const workflowOperation = this.getCurrentNodeParameter('workflowOperation') as string;
|
||||
if (workflowOperation !== 'dispatch') return { fields: [] };
|
||||
const operation = this.getCurrentNodeParameter('operation') as string;
|
||||
if (operation !== 'dispatchWorkflow') return { fields: [] };
|
||||
|
||||
const workflowId = this.getCurrentNodeParameter('workflowId') as string;
|
||||
if (!workflowId) return { fields: [] };
|
||||
|
||||
const credentials = await this.getCredentials('skyvernApi');
|
||||
const response = await makeRequest(credentials['baseUrl'] + '/api/v1/workflows/' + workflowId, {
|
||||
headers: {
|
||||
'x-api-key': credentials['apiKey'],
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Request to get workflow failed'); // eslint-disable-line
|
||||
}
|
||||
const workflow = await response.json();
|
||||
const workflow = await skyvernApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/api/v1/workflows/${workflowId}`,
|
||||
);
|
||||
const parameters: any[] = workflow.workflow_definition.parameters;
|
||||
|
||||
const fields: ResourceMapperField[] = await Promise.all(
|
||||
@@ -478,15 +457,11 @@ export class Skyvern implements INodeType {
|
||||
let options: INodePropertyOptions[] | undefined = undefined;
|
||||
let parameterType: FieldType | undefined = undefined;
|
||||
if (parameter.parameter_type === 'credential') {
|
||||
const credResponse = await makeRequest(credentials['baseUrl'] + '/api/v1/credentials', {
|
||||
headers: {
|
||||
'x-api-key': credentials['apiKey'],
|
||||
},
|
||||
});
|
||||
if (!credResponse.ok) {
|
||||
throw new Error('Request to get credentials failed'); // eslint-disable-line
|
||||
}
|
||||
const credData = await credResponse.json();
|
||||
const credData = await skyvernApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
'/api/v1/credentials',
|
||||
);
|
||||
options = credData.map((credential: any) => ({
|
||||
name: credential.name,
|
||||
value: credential.credential_id,
|
||||
|
||||
Reference in New Issue
Block a user