mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
Merge 91a827a4ec
into 8790a0df3d
This commit is contained in:
commit
b60955379c
|
@ -6,7 +6,7 @@ import type { ExecutionRepository } from '@/databases/repositories/execution.rep
|
||||||
import { WaitingForms } from '@/webhooks/waiting-forms';
|
import { WaitingForms } from '@/webhooks/waiting-forms';
|
||||||
|
|
||||||
import type { IExecutionResponse } from '../../interfaces';
|
import type { IExecutionResponse } from '../../interfaces';
|
||||||
import type { WaitingWebhookRequest } from '../webhook.types';
|
import type { IWebhookResponsePromiseData, WaitingWebhookRequest } from '../webhook.types';
|
||||||
|
|
||||||
describe('WaitingForms', () => {
|
describe('WaitingForms', () => {
|
||||||
const executionRepository = mock<ExecutionRepository>();
|
const executionRepository = mock<ExecutionRepository>();
|
||||||
|
@ -205,7 +205,7 @@ describe('WaitingForms', () => {
|
||||||
// @ts-expect-error Protected method
|
// @ts-expect-error Protected method
|
||||||
.spyOn(waitingForms, 'getWebhookExecutionData')
|
.spyOn(waitingForms, 'getWebhookExecutionData')
|
||||||
// @ts-expect-error Protected method
|
// @ts-expect-error Protected method
|
||||||
.mockResolvedValue(mock<IWebhookResponseCallbackData>());
|
.mockResolvedValue(mock<IWebhookResponsePromiseData>());
|
||||||
|
|
||||||
const execution = mock<IExecutionResponse>({
|
const execution = mock<IExecutionResponse>({
|
||||||
finished: false,
|
finished: false,
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { ConflictError } from '@/errors/response-errors/conflict.error';
|
||||||
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
||||||
import type { IExecutionResponse } from '@/interfaces';
|
import type { IExecutionResponse } from '@/interfaces';
|
||||||
import { WaitingWebhooks } from '@/webhooks/waiting-webhooks';
|
import { WaitingWebhooks } from '@/webhooks/waiting-webhooks';
|
||||||
import type { IWebhookResponseCallbackData, WaitingWebhookRequest } from '@/webhooks/webhook.types';
|
import type { IWebhookResponsePromiseData, WaitingWebhookRequest } from '@/webhooks/webhook.types';
|
||||||
|
|
||||||
describe('WaitingWebhooks', () => {
|
describe('WaitingWebhooks', () => {
|
||||||
const executionRepository = mock<ExecutionRepository>();
|
const executionRepository = mock<ExecutionRepository>();
|
||||||
|
@ -85,7 +85,7 @@ describe('WaitingWebhooks', () => {
|
||||||
// @ts-expect-error Protected method
|
// @ts-expect-error Protected method
|
||||||
.spyOn(waitingWebhooks, 'getWebhookExecutionData')
|
.spyOn(waitingWebhooks, 'getWebhookExecutionData')
|
||||||
// @ts-expect-error Protected method
|
// @ts-expect-error Protected method
|
||||||
.mockResolvedValue(mock<IWebhookResponseCallbackData>());
|
.mockResolvedValue(mock<IWebhookResponsePromiseData>());
|
||||||
|
|
||||||
const execution = mock<IExecutionResponse>({
|
const execution = mock<IExecutionResponse>({
|
||||||
finished: false,
|
finished: false,
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import { mock, type MockProxy } from 'jest-mock-extended';
|
import { mock, type MockProxy } from 'jest-mock-extended';
|
||||||
import type { Workflow, INode, IDataObject } from 'n8n-workflow';
|
import type { Workflow, INode, IN8nHttpFullResponse } from 'n8n-workflow';
|
||||||
import { FORM_NODE_TYPE, WAIT_NODE_TYPE } from 'n8n-workflow';
|
import { FORM_NODE_TYPE, WAIT_NODE_TYPE } from 'n8n-workflow';
|
||||||
|
|
||||||
import { autoDetectResponseMode, handleFormRedirectionCase } from '../webhook-helpers';
|
import { autoDetectResponseMode, handleFormRedirectionCase } from '../webhook-helpers';
|
||||||
import type { IWebhookResponseCallbackData } from '../webhook.types';
|
|
||||||
|
|
||||||
describe('autoDetectResponseMode', () => {
|
describe('autoDetectResponseMode', () => {
|
||||||
let workflow: MockProxy<Workflow>;
|
let workflow: MockProxy<Workflow>;
|
||||||
|
@ -58,40 +57,46 @@ describe('autoDetectResponseMode', () => {
|
||||||
|
|
||||||
describe('handleFormRedirectionCase', () => {
|
describe('handleFormRedirectionCase', () => {
|
||||||
test('should return data unchanged if start node is WAIT_NODE_TYPE with resume not equal to form', () => {
|
test('should return data unchanged if start node is WAIT_NODE_TYPE with resume not equal to form', () => {
|
||||||
const data: IWebhookResponseCallbackData = {
|
const response: IN8nHttpFullResponse = {
|
||||||
responseCode: 302,
|
statusCode: 302,
|
||||||
headers: { location: 'http://example.com' },
|
headers: { location: 'http://example.com' },
|
||||||
|
body: {},
|
||||||
};
|
};
|
||||||
const workflowStartNode = mock<INode>({
|
const workflowStartNode = mock<INode>({
|
||||||
type: WAIT_NODE_TYPE,
|
type: WAIT_NODE_TYPE,
|
||||||
parameters: { resume: 'webhook' },
|
parameters: { resume: 'webhook' },
|
||||||
});
|
});
|
||||||
const result = handleFormRedirectionCase(data, workflowStartNode);
|
const result = handleFormRedirectionCase(response, workflowStartNode);
|
||||||
expect(result).toEqual(data);
|
expect(result).toEqual(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should modify data if start node type matches and responseCode is a redirect', () => {
|
test('should modify data if start node type matches and responseCode is a redirect', () => {
|
||||||
const data: IWebhookResponseCallbackData = {
|
const response: IN8nHttpFullResponse = {
|
||||||
responseCode: 302,
|
statusCode: 302,
|
||||||
headers: { location: 'http://example.com' },
|
headers: { location: 'http://example.com' },
|
||||||
|
body: {},
|
||||||
};
|
};
|
||||||
const workflowStartNode = mock<INode>({
|
const workflowStartNode = mock<INode>({
|
||||||
type: FORM_NODE_TYPE,
|
type: FORM_NODE_TYPE,
|
||||||
parameters: {},
|
parameters: {},
|
||||||
});
|
});
|
||||||
const result = handleFormRedirectionCase(data, workflowStartNode);
|
const result = handleFormRedirectionCase(response, workflowStartNode);
|
||||||
expect(result.responseCode).toBe(200);
|
expect(result.statusCode).toBe(200);
|
||||||
expect(result.data).toEqual({ redirectURL: 'http://example.com' });
|
expect(result.body).toEqual({ redirectURL: 'http://example.com' });
|
||||||
expect((result?.headers as IDataObject)?.location).toBeUndefined();
|
expect(result.headers.location).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should not modify data if location header is missing', () => {
|
test('should not modify data if location header is missing', () => {
|
||||||
const data: IWebhookResponseCallbackData = { responseCode: 302, headers: {} };
|
const response: IN8nHttpFullResponse = {
|
||||||
|
statusCode: 302,
|
||||||
|
headers: {},
|
||||||
|
body: {},
|
||||||
|
};
|
||||||
const workflowStartNode = mock<INode>({
|
const workflowStartNode = mock<INode>({
|
||||||
type: FORM_NODE_TYPE,
|
type: FORM_NODE_TYPE,
|
||||||
parameters: {},
|
parameters: {},
|
||||||
});
|
});
|
||||||
const result = handleFormRedirectionCase(data, workflowStartNode);
|
const result = handleFormRedirectionCase(response, workflowStartNode);
|
||||||
expect(result).toEqual(data);
|
expect(result).toEqual(response);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { ResponseError } from '@/errors/response-errors/abstract/response.error'
|
||||||
import { createWebhookHandlerFor } from '@/webhooks/webhook-request-handler';
|
import { createWebhookHandlerFor } from '@/webhooks/webhook-request-handler';
|
||||||
import type {
|
import type {
|
||||||
IWebhookManager,
|
IWebhookManager,
|
||||||
IWebhookResponseCallbackData,
|
IWebhookResponsePromiseData,
|
||||||
WebhookOptionsRequest,
|
WebhookOptionsRequest,
|
||||||
WebhookRequest,
|
WebhookRequest,
|
||||||
} from '@/webhooks/webhook.types';
|
} from '@/webhooks/webhook.types';
|
||||||
|
@ -150,14 +150,16 @@ describe('WebhookRequestHandler', () => {
|
||||||
|
|
||||||
const res = mock<Response>();
|
const res = mock<Response>();
|
||||||
|
|
||||||
const executeWebhookResponse: IWebhookResponseCallbackData = {
|
const executeWebhookResponse: IWebhookResponsePromiseData = {
|
||||||
responseCode: 200,
|
response: {
|
||||||
data: {},
|
statusCode: 200,
|
||||||
|
body: {},
|
||||||
headers: {
|
headers: {
|
||||||
'x-custom-header': 'test',
|
'x-custom-header': 'test',
|
||||||
},
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
webhookManager.executeWebhook.mockResolvedValueOnce(executeWebhookResponse);
|
// webhookManager.executeWebhook.mockResolvedValueOnce(executeWebhookResponse);
|
||||||
|
|
||||||
await handler(req, res);
|
await handler(req, res);
|
||||||
|
|
||||||
|
@ -166,7 +168,7 @@ describe('WebhookRequestHandler', () => {
|
||||||
expect(res.header).toHaveBeenCalledWith({
|
expect(res.header).toHaveBeenCalledWith({
|
||||||
'x-custom-header': 'test',
|
'x-custom-header': 'test',
|
||||||
});
|
});
|
||||||
expect(res.json).toHaveBeenCalledWith(executeWebhookResponse.data);
|
expect(res.json).toHaveBeenCalledWith(executeWebhookResponse.response.body);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should send an error response if webhook execution throws', async () => {
|
it('should send an error response if webhook execution throws', async () => {
|
||||||
|
@ -204,16 +206,20 @@ describe('WebhookRequestHandler', () => {
|
||||||
|
|
||||||
const res = mock<Response>();
|
const res = mock<Response>();
|
||||||
|
|
||||||
const executeWebhookResponse: IWebhookResponseCallbackData = {
|
const executeWebhookResponse: IWebhookResponsePromiseData = {
|
||||||
responseCode: 200,
|
response: {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {},
|
||||||
|
body: {},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
webhookManager.executeWebhook.mockResolvedValueOnce(executeWebhookResponse);
|
// webhookManager.executeWebhook.mockResolvedValueOnce(executeWebhookResponse);
|
||||||
|
|
||||||
await handler(req, res);
|
await handler(req, res);
|
||||||
|
|
||||||
expect(webhookManager.executeWebhook).toHaveBeenCalledWith(req, res);
|
expect(webhookManager.executeWebhook).toHaveBeenCalledWith(req, res);
|
||||||
expect(res.status).toHaveBeenCalledWith(200);
|
expect(res.status).toHaveBeenCalledWith(200);
|
||||||
expect(res.json).toHaveBeenCalledWith(executeWebhookResponse.data);
|
expect(res.json).toHaveBeenCalledWith(executeWebhookResponse.response.body);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Service } from '@n8n/di';
|
import { Service } from '@n8n/di';
|
||||||
import type { Response } from 'express';
|
import type { Response } from 'express';
|
||||||
import { Logger } from 'n8n-core';
|
import { Logger } from 'n8n-core';
|
||||||
import { Workflow, CHAT_TRIGGER_NODE_TYPE } from 'n8n-workflow';
|
import { Workflow, CHAT_TRIGGER_NODE_TYPE, createDeferredPromise } from 'n8n-workflow';
|
||||||
import type { INode, IWebhookData, IHttpRequestMethods } from 'n8n-workflow';
|
import type { INode, IWebhookData, IHttpRequestMethods } from 'n8n-workflow';
|
||||||
|
|
||||||
import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
|
import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
|
||||||
|
@ -14,7 +14,7 @@ import * as WorkflowExecuteAdditionalData from '@/workflow-execute-additional-da
|
||||||
import { WorkflowStaticDataService } from '@/workflows/workflow-static-data.service';
|
import { WorkflowStaticDataService } from '@/workflows/workflow-static-data.service';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
IWebhookResponseCallbackData,
|
IWebhookResponsePromiseData,
|
||||||
IWebhookManager,
|
IWebhookManager,
|
||||||
WebhookAccessControlOptions,
|
WebhookAccessControlOptions,
|
||||||
WebhookRequest,
|
WebhookRequest,
|
||||||
|
@ -66,10 +66,7 @@ export class LiveWebhooks implements IWebhookManager {
|
||||||
/**
|
/**
|
||||||
* Checks if a webhook for the given method and path exists and executes the workflow.
|
* Checks if a webhook for the given method and path exists and executes the workflow.
|
||||||
*/
|
*/
|
||||||
async executeWebhook(
|
async executeWebhook(request: WebhookRequest, response: Response): Promise<void> {
|
||||||
request: WebhookRequest,
|
|
||||||
response: Response,
|
|
||||||
): Promise<IWebhookResponseCallbackData> {
|
|
||||||
const httpMethod = request.method;
|
const httpMethod = request.method;
|
||||||
const path = request.params.path;
|
const path = request.params.path;
|
||||||
|
|
||||||
|
@ -126,9 +123,9 @@ export class LiveWebhooks implements IWebhookManager {
|
||||||
throw new NotFoundError('Could not find node to process webhook.');
|
throw new NotFoundError('Could not find node to process webhook.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
const executionMode = 'webhook';
|
const executionMode = 'webhook';
|
||||||
void WebhookHelpers.executeWebhook(
|
const responsePromise = createDeferredPromise<IWebhookResponsePromiseData>();
|
||||||
|
await WebhookHelpers.executeWebhook(
|
||||||
workflow,
|
workflow,
|
||||||
webhookData,
|
webhookData,
|
||||||
workflowData,
|
workflowData,
|
||||||
|
@ -139,16 +136,11 @@ export class LiveWebhooks implements IWebhookManager {
|
||||||
undefined,
|
undefined,
|
||||||
request,
|
request,
|
||||||
response,
|
response,
|
||||||
async (error: Error | null, data: object) => {
|
responsePromise,
|
||||||
if (error !== null) {
|
);
|
||||||
return reject(error);
|
|
||||||
}
|
|
||||||
// Save static data if it changed
|
// Save static data if it changed
|
||||||
await this.workflowStaticDataService.saveStaticData(workflow);
|
await this.workflowStaticDataService.saveStaticData(workflow);
|
||||||
resolve(data);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async findWebhook(path: string, httpMethod: IHttpRequestMethods) {
|
private async findWebhook(path: string, httpMethod: IHttpRequestMethods) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Service } from '@n8n/di';
|
import { Service } from '@n8n/di';
|
||||||
import type express from 'express';
|
import type express from 'express';
|
||||||
import { InstanceSettings } from 'n8n-core';
|
import { InstanceSettings } from 'n8n-core';
|
||||||
import { WebhookPathTakenError, Workflow } from 'n8n-workflow';
|
import { createDeferredPromise, WebhookPathTakenError, Workflow } from 'n8n-workflow';
|
||||||
import type {
|
import type {
|
||||||
IWebhookData,
|
IWebhookData,
|
||||||
IWorkflowExecuteAdditionalData,
|
IWorkflowExecuteAdditionalData,
|
||||||
|
@ -26,7 +26,7 @@ import type { WorkflowRequest } from '@/workflows/workflow.request';
|
||||||
|
|
||||||
import { WebhookService } from './webhook.service';
|
import { WebhookService } from './webhook.service';
|
||||||
import type {
|
import type {
|
||||||
IWebhookResponseCallbackData,
|
IWebhookResponsePromiseData,
|
||||||
IWebhookManager,
|
IWebhookManager,
|
||||||
WebhookAccessControlOptions,
|
WebhookAccessControlOptions,
|
||||||
WebhookRequest,
|
WebhookRequest,
|
||||||
|
@ -53,10 +53,7 @@ export class TestWebhooks implements IWebhookManager {
|
||||||
* Return a promise that resolves when the test webhook is called.
|
* Return a promise that resolves when the test webhook is called.
|
||||||
* Also inform the FE of the result and remove the test webhook.
|
* Also inform the FE of the result and remove the test webhook.
|
||||||
*/
|
*/
|
||||||
async executeWebhook(
|
async executeWebhook(request: WebhookRequest, response: express.Response): Promise<void> {
|
||||||
request: WebhookRequest,
|
|
||||||
response: express.Response,
|
|
||||||
): Promise<IWebhookResponseCallbackData> {
|
|
||||||
const httpMethod = request.method;
|
const httpMethod = request.method;
|
||||||
|
|
||||||
let path = removeTrailingSlash(request.params.path);
|
let path = removeTrailingSlash(request.params.path);
|
||||||
|
@ -113,9 +110,9 @@ export class TestWebhooks implements IWebhookManager {
|
||||||
throw new NotFoundError('Could not find node to process webhook.');
|
throw new NotFoundError('Could not find node to process webhook.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return await new Promise(async (resolve, reject) => {
|
|
||||||
try {
|
|
||||||
const executionMode = 'manual';
|
const executionMode = 'manual';
|
||||||
|
const responsePromise = createDeferredPromise<IWebhookResponsePromiseData>();
|
||||||
|
try {
|
||||||
const executionId = await WebhookHelpers.executeWebhook(
|
const executionId = await WebhookHelpers.executeWebhook(
|
||||||
workflow,
|
workflow,
|
||||||
webhook,
|
webhook,
|
||||||
|
@ -127,10 +124,7 @@ export class TestWebhooks implements IWebhookManager {
|
||||||
undefined, // executionId
|
undefined, // executionId
|
||||||
request,
|
request,
|
||||||
response,
|
response,
|
||||||
(error: Error | null, data: IWebhookResponseCallbackData) => {
|
responsePromise,
|
||||||
if (error !== null) reject(error);
|
|
||||||
else resolve(data);
|
|
||||||
},
|
|
||||||
destinationNode,
|
destinationNode,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -165,7 +159,6 @@ export class TestWebhooks implements IWebhookManager {
|
||||||
this.clearTimeout(key);
|
this.clearTimeout(key);
|
||||||
|
|
||||||
await this.deactivateWebhooks(workflow);
|
await this.deactivateWebhooks(workflow);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clearTimeout(key: string) {
|
clearTimeout(key: string) {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
||||||
import type { IExecutionResponse } from '@/interfaces';
|
import type { IExecutionResponse } from '@/interfaces';
|
||||||
import { WaitingWebhooks } from '@/webhooks/waiting-webhooks';
|
import { WaitingWebhooks } from '@/webhooks/waiting-webhooks';
|
||||||
|
|
||||||
import type { IWebhookResponseCallbackData, WaitingWebhookRequest } from './webhook.types';
|
import type { WaitingWebhookRequest } from './webhook.types';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class WaitingForms extends WaitingWebhooks {
|
export class WaitingForms extends WaitingWebhooks {
|
||||||
|
@ -61,10 +61,7 @@ export class WaitingForms extends WaitingWebhooks {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async executeWebhook(
|
async executeWebhook(req: WaitingWebhookRequest, res: express.Response): Promise<void> {
|
||||||
req: WaitingWebhookRequest,
|
|
||||||
res: express.Response,
|
|
||||||
): Promise<IWebhookResponseCallbackData> {
|
|
||||||
const { path: executionId, suffix } = req.params;
|
const { path: executionId, suffix } = req.params;
|
||||||
|
|
||||||
this.logReceivedWebhook(req.method, executionId);
|
this.logReceivedWebhook(req.method, executionId);
|
||||||
|
@ -107,10 +104,7 @@ export class WaitingForms extends WaitingWebhooks {
|
||||||
message: 'Your response has been recorded',
|
message: 'Your response has been recorded',
|
||||||
formTitle: 'Form Submitted',
|
formTitle: 'Form Submitted',
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
return {
|
|
||||||
noWebhookResponse: true,
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
lastNodeExecuted = completionPage;
|
lastNodeExecuted = completionPage;
|
||||||
}
|
}
|
||||||
|
@ -122,7 +116,7 @@ export class WaitingForms extends WaitingWebhooks {
|
||||||
*/
|
*/
|
||||||
if (execution.mode === 'manual') execution.data.isTestWebhook = true;
|
if (execution.mode === 'manual') execution.data.isTestWebhook = true;
|
||||||
|
|
||||||
return await this.getWebhookExecutionData({
|
await this.getWebhookExecutionData({
|
||||||
execution,
|
execution,
|
||||||
req,
|
req,
|
||||||
res,
|
res,
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { Service } from '@n8n/di';
|
import { Service } from '@n8n/di';
|
||||||
import type express from 'express';
|
import type express from 'express';
|
||||||
import { Logger } from 'n8n-core';
|
import { Logger } from 'n8n-core';
|
||||||
|
import type { INodes, IWorkflowBase } from 'n8n-workflow';
|
||||||
import {
|
import {
|
||||||
|
createDeferredPromise,
|
||||||
FORM_NODE_TYPE,
|
FORM_NODE_TYPE,
|
||||||
type INodes,
|
|
||||||
type IWorkflowBase,
|
|
||||||
SEND_AND_WAIT_OPERATION,
|
SEND_AND_WAIT_OPERATION,
|
||||||
WAIT_NODE_TYPE,
|
WAIT_NODE_TYPE,
|
||||||
Workflow,
|
Workflow,
|
||||||
|
@ -20,7 +20,7 @@ import * as WorkflowExecuteAdditionalData from '@/workflow-execute-additional-da
|
||||||
|
|
||||||
import { WebhookService } from './webhook.service';
|
import { WebhookService } from './webhook.service';
|
||||||
import type {
|
import type {
|
||||||
IWebhookResponseCallbackData,
|
IWebhookResponsePromiseData,
|
||||||
IWebhookManager,
|
IWebhookManager,
|
||||||
WaitingWebhookRequest,
|
WaitingWebhookRequest,
|
||||||
} from './webhook.types';
|
} from './webhook.types';
|
||||||
|
@ -81,10 +81,7 @@ export class WaitingWebhooks implements IWebhookManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async executeWebhook(
|
async executeWebhook(req: WaitingWebhookRequest, res: express.Response): Promise<void> {
|
||||||
req: WaitingWebhookRequest,
|
|
||||||
res: express.Response,
|
|
||||||
): Promise<IWebhookResponseCallbackData> {
|
|
||||||
const { path: executionId, suffix } = req.params;
|
const { path: executionId, suffix } = req.params;
|
||||||
|
|
||||||
this.logReceivedWebhook(req.method, executionId);
|
this.logReceivedWebhook(req.method, executionId);
|
||||||
|
@ -113,7 +110,7 @@ export class WaitingWebhooks implements IWebhookManager {
|
||||||
const { nodes } = this.createWorkflow(workflowData);
|
const { nodes } = this.createWorkflow(workflowData);
|
||||||
if (this.isSendAndWaitRequest(nodes, suffix)) {
|
if (this.isSendAndWaitRequest(nodes, suffix)) {
|
||||||
res.render('send-and-wait-no-action-required', { isTestWebhook: false });
|
res.render('send-and-wait-no-action-required', { isTestWebhook: false });
|
||||||
return { noWebhookResponse: true };
|
return;
|
||||||
} else {
|
} else {
|
||||||
throw new ConflictError(`The execution "${executionId} has finished already.`);
|
throw new ConflictError(`The execution "${executionId} has finished already.`);
|
||||||
}
|
}
|
||||||
|
@ -127,7 +124,7 @@ export class WaitingWebhooks implements IWebhookManager {
|
||||||
*/
|
*/
|
||||||
if (execution.mode === 'manual') execution.data.isTestWebhook = true;
|
if (execution.mode === 'manual') execution.data.isTestWebhook = true;
|
||||||
|
|
||||||
return await this.getWebhookExecutionData({
|
await this.getWebhookExecutionData({
|
||||||
execution,
|
execution,
|
||||||
req,
|
req,
|
||||||
res,
|
res,
|
||||||
|
@ -151,7 +148,7 @@ export class WaitingWebhooks implements IWebhookManager {
|
||||||
lastNodeExecuted: string;
|
lastNodeExecuted: string;
|
||||||
executionId: string;
|
executionId: string;
|
||||||
suffix?: string;
|
suffix?: string;
|
||||||
}): Promise<IWebhookResponseCallbackData> {
|
}): Promise<void> {
|
||||||
// Set the node as disabled so that the data does not get executed again as it would result
|
// Set the node as disabled so that the data does not get executed again as it would result
|
||||||
// in starting the wait all over again
|
// in starting the wait all over again
|
||||||
this.disableNode(execution, req.method);
|
this.disableNode(execution, req.method);
|
||||||
|
@ -188,7 +185,7 @@ export class WaitingWebhooks implements IWebhookManager {
|
||||||
|
|
||||||
if (this.isSendAndWaitRequest(workflow.nodes, suffix)) {
|
if (this.isSendAndWaitRequest(workflow.nodes, suffix)) {
|
||||||
res.render('send-and-wait-no-action-required', { isTestWebhook: false });
|
res.render('send-and-wait-no-action-required', { isTestWebhook: false });
|
||||||
return { noWebhookResponse: true };
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!execution.data.resultData.error && execution.status === 'waiting') {
|
if (!execution.data.resultData.error && execution.status === 'waiting') {
|
||||||
|
@ -203,7 +200,7 @@ export class WaitingWebhooks implements IWebhookManager {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (hasChildForms) {
|
if (hasChildForms) {
|
||||||
return { noWebhookResponse: true };
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,9 +209,9 @@ export class WaitingWebhooks implements IWebhookManager {
|
||||||
|
|
||||||
const runExecutionData = execution.data;
|
const runExecutionData = execution.data;
|
||||||
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
const executionMode = 'webhook';
|
const executionMode = 'webhook';
|
||||||
void WebhookHelpers.executeWebhook(
|
const responsePromise = createDeferredPromise<IWebhookResponsePromiseData>();
|
||||||
|
await WebhookHelpers.executeWebhook(
|
||||||
workflow,
|
workflow,
|
||||||
webhookData,
|
webhookData,
|
||||||
workflowData,
|
workflowData,
|
||||||
|
@ -225,14 +222,7 @@ export class WaitingWebhooks implements IWebhookManager {
|
||||||
execution.id,
|
execution.id,
|
||||||
req,
|
req,
|
||||||
res,
|
res,
|
||||||
|
responsePromise,
|
||||||
(error: Error | null, data: object) => {
|
|
||||||
if (error !== null) {
|
|
||||||
return reject(error);
|
|
||||||
}
|
|
||||||
resolve(data);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,6 @@ import type {
|
||||||
import {
|
import {
|
||||||
ApplicationError,
|
ApplicationError,
|
||||||
BINARY_ENCODING,
|
BINARY_ENCODING,
|
||||||
createDeferredPromise,
|
|
||||||
ExecutionCancelledError,
|
ExecutionCancelledError,
|
||||||
FORM_NODE_TYPE,
|
FORM_NODE_TYPE,
|
||||||
FORM_TRIGGER_NODE_TYPE,
|
FORM_TRIGGER_NODE_TYPE,
|
||||||
|
@ -60,7 +59,7 @@ import * as WorkflowHelpers from '@/workflow-helpers';
|
||||||
import { WorkflowRunner } from '@/workflow-runner';
|
import { WorkflowRunner } from '@/workflow-runner';
|
||||||
|
|
||||||
import { WebhookService } from './webhook.service';
|
import { WebhookService } from './webhook.service';
|
||||||
import type { IWebhookResponseCallbackData, WebhookRequest } from './webhook.types';
|
import type { IWebhookResponsePromiseData, WebhookRequest } from './webhook.types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all the webhooks which should be created for the given workflow
|
* Returns all the webhooks which should be created for the given workflow
|
||||||
|
@ -107,7 +106,7 @@ export function autoDetectResponseMode(
|
||||||
workflowStartNode: INode,
|
workflowStartNode: INode,
|
||||||
workflow: Workflow,
|
workflow: Workflow,
|
||||||
method: string,
|
method: string,
|
||||||
) {
|
): WebhookResponseMode | undefined {
|
||||||
if (workflowStartNode.type === WAIT_NODE_TYPE && workflowStartNode.parameters.resume !== 'form') {
|
if (workflowStartNode.type === WAIT_NODE_TYPE && workflowStartNode.parameters.resume !== 'form') {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@ -137,26 +136,25 @@ export function autoDetectResponseMode(
|
||||||
* for formTrigger and form nodes redirection has to be handled by sending redirectURL in response body
|
* for formTrigger and form nodes redirection has to be handled by sending redirectURL in response body
|
||||||
*/
|
*/
|
||||||
export const handleFormRedirectionCase = (
|
export const handleFormRedirectionCase = (
|
||||||
data: IWebhookResponseCallbackData,
|
response: IN8nHttpFullResponse,
|
||||||
workflowStartNode: INode,
|
workflowStartNode: INode,
|
||||||
) => {
|
): IN8nHttpFullResponse => {
|
||||||
if (workflowStartNode.type === WAIT_NODE_TYPE && workflowStartNode.parameters.resume !== 'form') {
|
if (workflowStartNode.type === WAIT_NODE_TYPE && workflowStartNode.parameters.resume !== 'form') {
|
||||||
return data;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { headers, statusCode } = response;
|
||||||
if (
|
if (
|
||||||
[FORM_NODE_TYPE, FORM_TRIGGER_NODE_TYPE, WAIT_NODE_TYPE].includes(workflowStartNode.type) &&
|
[FORM_NODE_TYPE, FORM_TRIGGER_NODE_TYPE, WAIT_NODE_TYPE].includes(workflowStartNode.type) &&
|
||||||
(data?.headers as IDataObject)?.location &&
|
headers.location &&
|
||||||
String(data?.responseCode).startsWith('3')
|
String(statusCode).startsWith('3')
|
||||||
) {
|
) {
|
||||||
data.responseCode = 200;
|
response.statusCode = 200;
|
||||||
data.data = {
|
response.body = { redirectURL: headers.location };
|
||||||
redirectURL: (data?.headers as IDataObject)?.location,
|
delete headers.location;
|
||||||
};
|
|
||||||
(data.headers as IDataObject).location = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return response;
|
||||||
};
|
};
|
||||||
|
|
||||||
const { formDataFileSizeMax } = Container.get(GlobalConfig).endpoints;
|
const { formDataFileSizeMax } = Container.get(GlobalConfig).endpoints;
|
||||||
|
@ -177,7 +175,7 @@ export async function executeWebhook(
|
||||||
executionId: string | undefined,
|
executionId: string | undefined,
|
||||||
req: WebhookRequest,
|
req: WebhookRequest,
|
||||||
res: express.Response,
|
res: express.Response,
|
||||||
responseCallback: (error: Error | null, data: IWebhookResponseCallbackData) => void,
|
responsePromise: IDeferredPromise<IWebhookResponsePromiseData>,
|
||||||
destinationNode?: string,
|
destinationNode?: string,
|
||||||
): Promise<string | undefined> {
|
): Promise<string | undefined> {
|
||||||
// Get the nodeType to know which responseMode is set
|
// Get the nodeType to know which responseMode is set
|
||||||
|
@ -185,11 +183,6 @@ export async function executeWebhook(
|
||||||
workflowStartNode.type,
|
workflowStartNode.type,
|
||||||
workflowStartNode.typeVersion,
|
workflowStartNode.typeVersion,
|
||||||
);
|
);
|
||||||
if (nodeType === undefined) {
|
|
||||||
const errorMessage = `The type of the webhook node "${workflowStartNode.name}" is not known`;
|
|
||||||
responseCallback(new ApplicationError(errorMessage), {});
|
|
||||||
throw new InternalServerError(errorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
const additionalKeys: IWorkflowDataProxyAdditionalKeys = {
|
const additionalKeys: IWorkflowDataProxyAdditionalKeys = {
|
||||||
$executionId: executionId,
|
$executionId: executionId,
|
||||||
|
@ -209,24 +202,28 @@ export async function executeWebhook(
|
||||||
additionalData.executionId = executionId;
|
additionalData.executionId = executionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the responseMode
|
|
||||||
let responseMode;
|
|
||||||
|
|
||||||
//check if response mode should be set automatically, e.g. multipage form
|
//check if response mode should be set automatically, e.g. multipage form
|
||||||
responseMode = autoDetectResponseMode(workflowStartNode, workflow, req.method);
|
const responseMode =
|
||||||
|
autoDetectResponseMode(workflowStartNode, workflow, req.method) ??
|
||||||
if (!responseMode) {
|
(workflow.expression.getSimpleParameterValue(
|
||||||
responseMode = workflow.expression.getSimpleParameterValue(
|
|
||||||
workflowStartNode,
|
workflowStartNode,
|
||||||
webhookData.webhookDescription.responseMode,
|
webhookData.webhookDescription.responseMode,
|
||||||
executionMode,
|
executionMode,
|
||||||
additionalKeys,
|
additionalKeys,
|
||||||
undefined,
|
undefined,
|
||||||
'onReceived',
|
'onReceived',
|
||||||
) as WebhookResponseMode;
|
) as WebhookResponseMode);
|
||||||
|
|
||||||
|
if (!['onReceived', 'lastNode', 'responseNode'].includes(responseMode)) {
|
||||||
|
// If the mode is not known we error. Is probably best like that instead of using
|
||||||
|
// the default that people know as early as possible (probably already testing phase)
|
||||||
|
// that something does not resolve properly.
|
||||||
|
const errorMessage = `The response mode '${responseMode}' is not valid!`;
|
||||||
|
responsePromise.reject(new ApplicationError(errorMessage));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const responseCode = workflow.expression.getSimpleParameterValue(
|
const responseCodeParam = workflow.expression.getSimpleParameterValue(
|
||||||
workflowStartNode,
|
workflowStartNode,
|
||||||
webhookData.webhookDescription.responseCode as string,
|
webhookData.webhookDescription.responseCode as string,
|
||||||
executionMode,
|
executionMode,
|
||||||
|
@ -235,7 +232,7 @@ export async function executeWebhook(
|
||||||
200,
|
200,
|
||||||
) as number;
|
) as number;
|
||||||
|
|
||||||
const responseData = workflow.expression.getComplexParameterValue(
|
const responseDataParam = workflow.expression.getComplexParameterValue(
|
||||||
workflowStartNode,
|
workflowStartNode,
|
||||||
webhookData.webhookDescription.responseData,
|
webhookData.webhookDescription.responseData,
|
||||||
executionMode,
|
executionMode,
|
||||||
|
@ -244,35 +241,24 @@ export async function executeWebhook(
|
||||||
'firstEntryJson',
|
'firstEntryJson',
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!['onReceived', 'lastNode', 'responseNode'].includes(responseMode)) {
|
|
||||||
// If the mode is not known we error. Is probably best like that instead of using
|
|
||||||
// the default that people know as early as possible (probably already testing phase)
|
|
||||||
// that something does not resolve properly.
|
|
||||||
const errorMessage = `The response mode '${responseMode}' is not valid!`;
|
|
||||||
responseCallback(new ApplicationError(errorMessage), {});
|
|
||||||
throw new InternalServerError(errorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the Response and Request so that this data can be accessed in the node
|
|
||||||
additionalData.httpRequest = req;
|
|
||||||
additionalData.httpResponse = res;
|
|
||||||
|
|
||||||
let binaryData;
|
|
||||||
|
|
||||||
const nodeVersion = workflowStartNode.typeVersion;
|
const nodeVersion = workflowStartNode.typeVersion;
|
||||||
if (nodeVersion === 1) {
|
|
||||||
// binaryData option is removed in versions higher than 1
|
// binaryData option is removed in versions higher than 1
|
||||||
binaryData = workflow.expression.getSimpleParameterValue(
|
const binaryDataParam =
|
||||||
|
nodeVersion === 1
|
||||||
|
? (workflow.expression.getSimpleParameterValue(
|
||||||
workflowStartNode,
|
workflowStartNode,
|
||||||
'={{$parameter["options"]["binaryData"]}}',
|
'={{$parameter["options"]["binaryData"]}}',
|
||||||
executionMode,
|
executionMode,
|
||||||
additionalKeys,
|
additionalKeys,
|
||||||
undefined,
|
undefined,
|
||||||
false,
|
false,
|
||||||
);
|
) as boolean)
|
||||||
}
|
: undefined;
|
||||||
|
|
||||||
|
// Add the Response and Request so that this data can be accessed in the node
|
||||||
|
additionalData.httpRequest = req;
|
||||||
|
additionalData.httpResponse = res;
|
||||||
|
|
||||||
let didSendResponse = false;
|
|
||||||
let runExecutionDataMerge = {};
|
let runExecutionDataMerge = {};
|
||||||
try {
|
try {
|
||||||
// Run the webhook function to see what should be returned and if
|
// Run the webhook function to see what should be returned and if
|
||||||
|
@ -281,7 +267,7 @@ export async function executeWebhook(
|
||||||
|
|
||||||
// if `Webhook` or `Wait` node, and binaryData is enabled, skip pre-parse the request-body
|
// if `Webhook` or `Wait` node, and binaryData is enabled, skip pre-parse the request-body
|
||||||
// always falsy for versions higher than 1
|
// always falsy for versions higher than 1
|
||||||
if (!binaryData) {
|
if (!binaryDataParam) {
|
||||||
const { contentType } = req;
|
const { contentType } = req;
|
||||||
if (contentType === 'multipart/form-data') {
|
if (contentType === 'multipart/form-data') {
|
||||||
req.body = await parseFormData(req);
|
req.body = await parseFormData(req);
|
||||||
|
@ -336,9 +322,6 @@ export async function executeWebhook(
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
responseCallback(new ApplicationError(errorMessage), {});
|
|
||||||
didSendResponse = true;
|
|
||||||
|
|
||||||
// Add error to execution data that it can be logged and send to Editor-UI
|
// Add error to execution data that it can be logged and send to Editor-UI
|
||||||
runExecutionDataMerge = {
|
runExecutionDataMerge = {
|
||||||
resultData: {
|
resultData: {
|
||||||
|
@ -358,16 +341,20 @@ export async function executeWebhook(
|
||||||
// which then so gets the chance to throw the error.
|
// which then so gets the chance to throw the error.
|
||||||
workflowData: [[{ json: {} }]],
|
workflowData: [[{ json: {} }]],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
responsePromise.reject(new ApplicationError(errorMessage));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const additionalKeys: IWorkflowDataProxyAdditionalKeys = {
|
const additionalKeys: IWorkflowDataProxyAdditionalKeys = {
|
||||||
$executionId: executionId,
|
$executionId: executionId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const responseHeadersParam = webhookData.webhookDescription.responseHeaders;
|
||||||
if (webhookData.webhookDescription.responseHeaders !== undefined) {
|
if (webhookData.webhookDescription.responseHeaders !== undefined) {
|
||||||
const responseHeaders = workflow.expression.getComplexParameterValue(
|
const responseHeaders = workflow.expression.getComplexParameterValue(
|
||||||
workflowStartNode,
|
workflowStartNode,
|
||||||
webhookData.webhookDescription.responseHeaders,
|
responseHeadersParam,
|
||||||
executionMode,
|
executionMode,
|
||||||
additionalKeys,
|
additionalKeys,
|
||||||
undefined,
|
undefined,
|
||||||
|
@ -381,90 +368,83 @@ export async function executeWebhook(
|
||||||
| undefined;
|
| undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (responseHeaders !== undefined && responseHeaders.entries !== undefined) {
|
if (responseHeaders?.entries?.length) {
|
||||||
for (const item of responseHeaders.entries) {
|
for (const item of responseHeaders.entries) {
|
||||||
res.setHeader(item.name, item.value);
|
res.setHeader(item.name, item.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (webhookResultData.noWebhookResponse === true && !didSendResponse) {
|
if (webhookResultData.noWebhookResponse === true) {
|
||||||
// The response got already send
|
// The response got already send
|
||||||
responseCallback(null, {
|
responsePromise.resolve({ noWebhookResponse: true });
|
||||||
noWebhookResponse: true,
|
return;
|
||||||
});
|
|
||||||
didSendResponse = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (webhookResultData.workflowData === undefined) {
|
if (webhookResultData.workflowData === undefined) {
|
||||||
// Workflow should not run
|
// Workflow should not run
|
||||||
if (webhookResultData.webhookResponse !== undefined) {
|
if (webhookResultData.webhookResponse !== undefined) {
|
||||||
// Data to respond with is given
|
// Data to respond with is given
|
||||||
if (!didSendResponse) {
|
responsePromise.resolve({
|
||||||
responseCallback(null, {
|
response: {
|
||||||
data: webhookResultData.webhookResponse,
|
statusCode: responseCodeParam,
|
||||||
responseCode,
|
body: webhookResultData.webhookResponse,
|
||||||
|
headers: {},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
didSendResponse = true;
|
return;
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Send default response
|
// Send default response
|
||||||
|
responsePromise.resolve({
|
||||||
if (!didSendResponse) {
|
response: {
|
||||||
responseCallback(null, {
|
statusCode: responseCodeParam,
|
||||||
data: {
|
body: { message: 'Webhook call received' },
|
||||||
message: 'Webhook call received',
|
headers: {},
|
||||||
},
|
},
|
||||||
responseCode,
|
|
||||||
});
|
});
|
||||||
didSendResponse = true;
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that we know that the workflow should run we can return the default response
|
// Now that we know that the workflow should run we can return the default response
|
||||||
// directly if responseMode it set to "onReceived" and a response should be sent
|
// directly if responseMode it set to "onReceived" and a response should be sent
|
||||||
if (responseMode === 'onReceived' && !didSendResponse) {
|
if (responseMode === 'onReceived') {
|
||||||
// Return response directly and do not wait for the workflow to finish
|
void responsePromise.promise.then(async (resolveData) => {
|
||||||
if (responseData === 'noData') {
|
if (resolveData.noWebhookResponse) {
|
||||||
// Return without data
|
// TODO: send 204
|
||||||
responseCallback(null, {
|
res.end();
|
||||||
responseCode,
|
return;
|
||||||
});
|
|
||||||
} else if (responseData) {
|
|
||||||
// Return the data specified in the response data option
|
|
||||||
responseCallback(null, {
|
|
||||||
data: responseData as IDataObject,
|
|
||||||
responseCode,
|
|
||||||
});
|
|
||||||
} else if (webhookResultData.webhookResponse !== undefined) {
|
|
||||||
// Data to respond with is given
|
|
||||||
responseCallback(null, {
|
|
||||||
data: webhookResultData.webhookResponse,
|
|
||||||
responseCode,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
responseCallback(null, {
|
|
||||||
data: {
|
|
||||||
message: 'Workflow was started',
|
|
||||||
},
|
|
||||||
responseCode,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
const { statusCode, headers, body } = resolveData.response;
|
||||||
|
for (const [key, value] of Object.entries(headers)) {
|
||||||
|
res.setHeader(key, value);
|
||||||
|
}
|
||||||
|
// TODO handle binary responses
|
||||||
|
res.status(statusCode).json(body);
|
||||||
|
});
|
||||||
|
|
||||||
didSendResponse = true;
|
responsePromise.resolve({
|
||||||
|
response: {
|
||||||
|
statusCode: responseCodeParam,
|
||||||
|
headers: {},
|
||||||
|
body: responseDataParam ??
|
||||||
|
webhookResultData.webhookResponse ?? { message: 'Workflow was started' },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the data of the webhook node
|
// Initialize the data of the webhook node
|
||||||
const nodeExecutionStack: IExecuteData[] = [];
|
const nodeExecutionStack: IExecuteData[] = [
|
||||||
nodeExecutionStack.push({
|
{
|
||||||
node: workflowStartNode,
|
node: workflowStartNode,
|
||||||
data: {
|
data: {
|
||||||
main: webhookResultData.workflowData,
|
main: webhookResultData.workflowData,
|
||||||
},
|
},
|
||||||
source: null,
|
source: null,
|
||||||
});
|
},
|
||||||
|
];
|
||||||
|
|
||||||
runExecutionData =
|
runExecutionData =
|
||||||
runExecutionData ||
|
runExecutionData ||
|
||||||
|
@ -517,43 +497,34 @@ export async function executeWebhook(
|
||||||
runData.pushRef = runExecutionData.pushRef;
|
runData.pushRef = runExecutionData.pushRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
let responsePromise: IDeferredPromise<IN8nHttpFullResponse> | undefined;
|
|
||||||
if (responseMode === 'responseNode') {
|
if (responseMode === 'responseNode') {
|
||||||
responsePromise = createDeferredPromise<IN8nHttpFullResponse>();
|
|
||||||
responsePromise.promise
|
responsePromise.promise
|
||||||
.then(async (response: IN8nHttpFullResponse) => {
|
.then(async (resolveData) => {
|
||||||
if (didSendResponse) {
|
if (resolveData.noWebhookResponse) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { response } = resolveData;
|
||||||
const binaryData = (response.body as IDataObject)?.binaryData as IBinaryData;
|
const binaryData = (response.body as IDataObject)?.binaryData as IBinaryData;
|
||||||
if (binaryData?.id) {
|
if (binaryData?.id) {
|
||||||
res.header(response.headers);
|
res.header(response.headers);
|
||||||
const stream = await Container.get(BinaryDataService).getAsStream(binaryData.id);
|
const stream = await Container.get(BinaryDataService).getAsStream(binaryData.id);
|
||||||
stream.pipe(res, { end: false });
|
stream.pipe(res, { end: false });
|
||||||
await finished(stream);
|
await finished(stream);
|
||||||
responseCallback(null, { noWebhookResponse: true });
|
responsePromise.resolve({ noWebhookResponse: true });
|
||||||
} else if (Buffer.isBuffer(response.body)) {
|
} else if (Buffer.isBuffer(response.body)) {
|
||||||
res.header(response.headers);
|
res.header(response.headers);
|
||||||
res.end(response.body);
|
res.write(response.body);
|
||||||
responseCallback(null, { noWebhookResponse: true });
|
responsePromise.resolve({ noWebhookResponse: true });
|
||||||
} else {
|
} else {
|
||||||
// TODO: This probably needs some more changes depending on the options on the
|
// TODO: This probably needs some more changes depending on the options on the
|
||||||
// Webhook Response node
|
// Webhook Response node
|
||||||
|
let data: IWebhookResponsePromiseData = { response };
|
||||||
let data: IWebhookResponseCallbackData = {
|
data.response = handleFormRedirectionCase(data.response, workflowStartNode);
|
||||||
data: response.body as IDataObject,
|
responsePromise.resolve(data);
|
||||||
headers: response.headers,
|
|
||||||
responseCode: response.statusCode,
|
|
||||||
};
|
|
||||||
|
|
||||||
data = handleFormRedirectionCase(data, workflowStartNode);
|
|
||||||
|
|
||||||
responseCallback(null, data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
process.nextTick(() => res.end());
|
process.nextTick(() => res.end());
|
||||||
didSendResponse = true;
|
|
||||||
})
|
})
|
||||||
.catch(async (error) => {
|
.catch(async (error) => {
|
||||||
Container.get(ErrorReporter).error(error);
|
Container.get(ErrorReporter).error(error);
|
||||||
|
@ -561,7 +532,7 @@ export async function executeWebhook(
|
||||||
`Error with Webhook-Response for execution "${executionId}": "${error.message}"`,
|
`Error with Webhook-Response for execution "${executionId}": "${error.message}"`,
|
||||||
{ executionId, workflowId: workflow.id },
|
{ executionId, workflowId: workflow.id },
|
||||||
);
|
);
|
||||||
responseCallback(error, {});
|
responsePromise.reject(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -578,7 +549,7 @@ export async function executeWebhook(
|
||||||
executionId = await Container.get(WorkflowRunner).run(
|
executionId = await Container.get(WorkflowRunner).run(
|
||||||
runData,
|
runData,
|
||||||
true,
|
true,
|
||||||
!didSendResponse,
|
true,
|
||||||
executionId,
|
executionId,
|
||||||
responsePromise,
|
responsePromise,
|
||||||
);
|
);
|
||||||
|
@ -602,21 +573,20 @@ export async function executeWebhook(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!didSendResponse) {
|
|
||||||
executePromise
|
executePromise
|
||||||
// eslint-disable-next-line complexity
|
// eslint-disable-next-line complexity
|
||||||
.then(async (data) => {
|
.then(async (data) => {
|
||||||
if (data === undefined) {
|
if (data === undefined) {
|
||||||
if (!didSendResponse) {
|
responsePromise.resolve({
|
||||||
responseCallback(null, {
|
response: {
|
||||||
data: {
|
body: {
|
||||||
message: 'Workflow executed successfully but no data was returned',
|
message: 'Workflow executed successfully but no data was returned',
|
||||||
},
|
},
|
||||||
responseCode,
|
statusCode: responseCodeParam,
|
||||||
|
headers: {},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
didSendResponse = true;
|
return;
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (usePinData) {
|
if (usePinData) {
|
||||||
|
@ -625,15 +595,15 @@ export async function executeWebhook(
|
||||||
|
|
||||||
const returnData = WorkflowHelpers.getDataLastExecutedNodeData(data);
|
const returnData = WorkflowHelpers.getDataLastExecutedNodeData(data);
|
||||||
if (data.data.resultData.error || returnData?.error !== undefined) {
|
if (data.data.resultData.error || returnData?.error !== undefined) {
|
||||||
if (!didSendResponse) {
|
responsePromise.resolve({
|
||||||
responseCallback(null, {
|
response: {
|
||||||
data: {
|
body: {
|
||||||
message: 'Error in workflow',
|
message: 'Error in workflow',
|
||||||
},
|
},
|
||||||
responseCode: 500,
|
statusCode: 500,
|
||||||
|
headers: {},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
|
||||||
didSendResponse = true;
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -644,16 +614,15 @@ export async function executeWebhook(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (returnData === undefined) {
|
if (returnData === undefined) {
|
||||||
if (!didSendResponse) {
|
responsePromise.resolve({
|
||||||
responseCallback(null, {
|
response: {
|
||||||
data: {
|
body: {
|
||||||
message:
|
message: 'Workflow executed successfully but the last node did not return any data',
|
||||||
'Workflow executed successfully but the last node did not return any data',
|
},
|
||||||
|
statusCode: responseCodeParam,
|
||||||
|
headers: {},
|
||||||
},
|
},
|
||||||
responseCode,
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
didSendResponse = true;
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -661,19 +630,17 @@ export async function executeWebhook(
|
||||||
$executionId: executionId,
|
$executionId: executionId,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!didSendResponse) {
|
let responseData: IDataObject | IDataObject[] | undefined;
|
||||||
let data: IDataObject | IDataObject[] | undefined;
|
|
||||||
|
|
||||||
if (responseData === 'firstEntryJson') {
|
if (responseDataParam === 'firstEntryJson') {
|
||||||
// Return the JSON data of the first entry
|
// Return the JSON data of the first entry
|
||||||
|
|
||||||
if (returnData.data!.main[0]![0] === undefined) {
|
if (returnData.data!.main[0]![0] === undefined) {
|
||||||
responseCallback(new ApplicationError('No item to return got found'), {});
|
responsePromise.reject(new ApplicationError('No item to return got found'));
|
||||||
didSendResponse = true;
|
return;
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data = returnData.data!.main[0]![0].json;
|
responseData = returnData.data!.main[0]![0].json;
|
||||||
|
|
||||||
const responsePropertyName = workflow.expression.getSimpleParameterValue(
|
const responsePropertyName = workflow.expression.getSimpleParameterValue(
|
||||||
workflowStartNode,
|
workflowStartNode,
|
||||||
|
@ -685,7 +652,7 @@ export async function executeWebhook(
|
||||||
);
|
);
|
||||||
|
|
||||||
if (responsePropertyName !== undefined) {
|
if (responsePropertyName !== undefined) {
|
||||||
data = get(data, responsePropertyName as string) as IDataObject;
|
responseData = get(responseData, responsePropertyName as string) as IDataObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
const responseContentType = workflow.expression.getSimpleParameterValue(
|
const responseContentType = workflow.expression.getSimpleParameterValue(
|
||||||
|
@ -703,34 +670,30 @@ export async function executeWebhook(
|
||||||
|
|
||||||
// Returning an object, boolean, number, ... causes problems so make sure to stringify if needed
|
// Returning an object, boolean, number, ... causes problems so make sure to stringify if needed
|
||||||
if (
|
if (
|
||||||
data !== null &&
|
responseData !== null &&
|
||||||
data !== undefined &&
|
responseData !== undefined &&
|
||||||
['Buffer', 'String'].includes(data.constructor.name)
|
['Buffer', 'String'].includes(responseData.constructor.name)
|
||||||
) {
|
) {
|
||||||
res.end(data);
|
res.end(responseData);
|
||||||
} else {
|
} else {
|
||||||
res.end(JSON.stringify(data));
|
res.end(JSON.stringify(responseData));
|
||||||
}
|
}
|
||||||
|
|
||||||
responseCallback(null, {
|
responsePromise.resolve({ noWebhookResponse: true });
|
||||||
noWebhookResponse: true,
|
return;
|
||||||
});
|
|
||||||
didSendResponse = true;
|
|
||||||
}
|
}
|
||||||
} else if (responseData === 'firstEntryBinary') {
|
} else if (responseDataParam === 'firstEntryBinary') {
|
||||||
// Return the binary data of the first entry
|
// Return the binary data of the first entry
|
||||||
data = returnData.data!.main[0]![0];
|
responseData = returnData.data!.main[0]![0];
|
||||||
|
|
||||||
if (data === undefined) {
|
if (responseData === undefined) {
|
||||||
responseCallback(new ApplicationError('No item was found to return'), {});
|
responsePromise.reject(new ApplicationError('No item was found to return'));
|
||||||
didSendResponse = true;
|
return;
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.binary === undefined) {
|
if (responseData.binary === undefined) {
|
||||||
responseCallback(new ApplicationError('No binary data was found to return'), {});
|
responsePromise.reject(new ApplicationError('No binary data was found to return'));
|
||||||
didSendResponse = true;
|
return;
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const responseBinaryPropertyName = workflow.expression.getSimpleParameterValue(
|
const responseBinaryPropertyName = workflow.expression.getSimpleParameterValue(
|
||||||
|
@ -742,28 +705,23 @@ export async function executeWebhook(
|
||||||
'data',
|
'data',
|
||||||
);
|
);
|
||||||
|
|
||||||
if (responseBinaryPropertyName === undefined && !didSendResponse) {
|
if (responseBinaryPropertyName === undefined) {
|
||||||
responseCallback(
|
responsePromise.reject(new ApplicationError("No 'responseBinaryPropertyName' is set"));
|
||||||
new ApplicationError("No 'responseBinaryPropertyName' is set"),
|
return;
|
||||||
{},
|
|
||||||
);
|
|
||||||
didSendResponse = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const binaryData = (data.binary as IBinaryKeyData)[
|
const binaryData = (responseData.binary as IBinaryKeyData)[
|
||||||
responseBinaryPropertyName as string
|
responseBinaryPropertyName as string
|
||||||
];
|
];
|
||||||
if (binaryData === undefined && !didSendResponse) {
|
if (binaryData === undefined) {
|
||||||
responseCallback(
|
responsePromise.reject(
|
||||||
new ApplicationError(
|
new ApplicationError(
|
||||||
`The binary property '${responseBinaryPropertyName}' which should be returned does not exist`,
|
`The binary property '${responseBinaryPropertyName}' which should be returned does not exist`,
|
||||||
),
|
),
|
||||||
{},
|
|
||||||
);
|
);
|
||||||
didSendResponse = true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!didSendResponse) {
|
|
||||||
// Send the webhook response manually
|
// Send the webhook response manually
|
||||||
res.setHeader('Content-Type', binaryData.mimeType);
|
res.setHeader('Content-Type', binaryData.mimeType);
|
||||||
if (binaryData.id) {
|
if (binaryData.id) {
|
||||||
|
@ -774,49 +732,41 @@ export async function executeWebhook(
|
||||||
res.write(Buffer.from(binaryData.data, BINARY_ENCODING));
|
res.write(Buffer.from(binaryData.data, BINARY_ENCODING));
|
||||||
}
|
}
|
||||||
|
|
||||||
responseCallback(null, {
|
responsePromise.resolve({ noWebhookResponse: true });
|
||||||
noWebhookResponse: true,
|
|
||||||
});
|
|
||||||
process.nextTick(() => res.end());
|
process.nextTick(() => res.end());
|
||||||
}
|
} else if (responseDataParam === 'noData') {
|
||||||
} else if (responseData === 'noData') {
|
|
||||||
// Return without data
|
// Return without data
|
||||||
data = undefined;
|
responseData = undefined;
|
||||||
} else {
|
} else {
|
||||||
// Return the JSON data of all the entries
|
// Return the JSON data of all the entries
|
||||||
data = [];
|
responseData = [];
|
||||||
for (const entry of returnData.data!.main[0]!) {
|
for (const entry of returnData.data!.main[0]!) {
|
||||||
data.push(entry.json);
|
responseData.push(entry.json);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!didSendResponse) {
|
responsePromise.resolve({
|
||||||
responseCallback(null, {
|
response: {
|
||||||
data,
|
body: responseData,
|
||||||
responseCode,
|
statusCode: responseCodeParam,
|
||||||
|
headers: {},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
didSendResponse = true;
|
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
if (!didSendResponse) {
|
responsePromise.reject(
|
||||||
responseCallback(
|
|
||||||
new ApplicationError('There was a problem executing the workflow', {
|
new ApplicationError('There was a problem executing the workflow', {
|
||||||
level: 'warning',
|
level: 'warning',
|
||||||
cause: e,
|
cause: e,
|
||||||
}),
|
}),
|
||||||
{},
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
const internalServerError = new InternalServerError(e.message, e);
|
const internalServerError = new InternalServerError(e.message, e);
|
||||||
if (e instanceof ExecutionCancelledError) internalServerError.level = 'warning';
|
if (e instanceof ExecutionCancelledError) internalServerError.level = 'warning';
|
||||||
throw internalServerError;
|
throw internalServerError;
|
||||||
});
|
});
|
||||||
}
|
|
||||||
return executionId;
|
return executionId;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const error =
|
const error =
|
||||||
|
@ -826,8 +776,7 @@ export async function executeWebhook(
|
||||||
level: 'warning',
|
level: 'warning',
|
||||||
cause: e,
|
cause: e,
|
||||||
});
|
});
|
||||||
if (didSendResponse) throw error;
|
responsePromise.reject(error);
|
||||||
responseCallback(error, {});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,18 +42,7 @@ class WebhookRequestHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.webhookManager.executeWebhook(req, res);
|
await this.webhookManager.executeWebhook(req, res);
|
||||||
|
|
||||||
// Don't respond, if already responded
|
|
||||||
if (response.noWebhookResponse !== true) {
|
|
||||||
ResponseHelper.sendSuccessResponse(
|
|
||||||
res,
|
|
||||||
response.data,
|
|
||||||
true,
|
|
||||||
response.responseCode,
|
|
||||||
response.headers,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const error = ensureError(e);
|
const error = ensureError(e);
|
||||||
Container.get(Logger).debug(
|
Container.get(Logger).debug(
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import type { Request, Response } from 'express';
|
import type { Request, Response } from 'express';
|
||||||
import type { IDataObject, IHttpRequestMethods } from 'n8n-workflow';
|
import type { IHttpRequestMethods, IN8nHttpFullResponse } from 'n8n-workflow';
|
||||||
|
|
||||||
export type WebhookOptionsRequest = Request & { method: 'OPTIONS' };
|
export type WebhookOptionsRequest = Request & { method: 'OPTIONS' };
|
||||||
|
|
||||||
|
@ -26,12 +26,12 @@ export interface IWebhookManager {
|
||||||
httpMethod: IHttpRequestMethods,
|
httpMethod: IHttpRequestMethods,
|
||||||
) => Promise<WebhookAccessControlOptions | undefined>;
|
) => Promise<WebhookAccessControlOptions | undefined>;
|
||||||
|
|
||||||
executeWebhook(req: WebhookRequest, res: Response): Promise<IWebhookResponseCallbackData>;
|
executeWebhook(req: WebhookRequest, res: Response): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IWebhookResponseCallbackData {
|
export type IWebhookResponsePromiseData =
|
||||||
data?: IDataObject | IDataObject[];
|
| { noWebhookResponse: true }
|
||||||
headers?: object;
|
| {
|
||||||
noWebhookResponse?: boolean;
|
noWebhookResponse?: false;
|
||||||
responseCode?: number;
|
response: IN8nHttpFullResponse;
|
||||||
}
|
};
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { GlobalConfig } from '@n8n/config';
|
import { GlobalConfig } from '@n8n/config';
|
||||||
import { Container } from '@n8n/di';
|
import { Container } from '@n8n/di';
|
||||||
import { mock } from 'jest-mock-extended';
|
|
||||||
import { agent as testAgent } from 'supertest';
|
import { agent as testAgent } from 'supertest';
|
||||||
import type SuperAgentTest from 'supertest/lib/agent';
|
import type SuperAgentTest from 'supertest/lib/agent';
|
||||||
|
|
||||||
|
@ -10,7 +9,6 @@ import { TestWebhooks } from '@/webhooks/test-webhooks';
|
||||||
import { WaitingForms } from '@/webhooks/waiting-forms';
|
import { WaitingForms } from '@/webhooks/waiting-forms';
|
||||||
import { WaitingWebhooks } from '@/webhooks/waiting-webhooks';
|
import { WaitingWebhooks } from '@/webhooks/waiting-webhooks';
|
||||||
import { WebhookServer } from '@/webhooks/webhook-server';
|
import { WebhookServer } from '@/webhooks/webhook-server';
|
||||||
import type { IWebhookResponseCallbackData } from '@/webhooks/webhook.types';
|
|
||||||
import { mockInstance } from '@test/mocking';
|
import { mockInstance } from '@test/mocking';
|
||||||
|
|
||||||
let agent: SuperAgentTest;
|
let agent: SuperAgentTest;
|
||||||
|
@ -60,9 +58,6 @@ describe('WebhookServer', () => {
|
||||||
it('should handle regular requests', async () => {
|
it('should handle regular requests', async () => {
|
||||||
const pathPrefix = Container.get(GlobalConfig).endpoints[key];
|
const pathPrefix = Container.get(GlobalConfig).endpoints[key];
|
||||||
manager.getWebhookMethods.mockResolvedValueOnce(['GET']);
|
manager.getWebhookMethods.mockResolvedValueOnce(['GET']);
|
||||||
manager.executeWebhook.mockResolvedValueOnce(
|
|
||||||
mockResponse({ test: true }, { key: 'value ' }),
|
|
||||||
);
|
|
||||||
|
|
||||||
const response = await agent
|
const response = await agent
|
||||||
.get(`/${pathPrefix}/abcd`)
|
.get(`/${pathPrefix}/abcd`)
|
||||||
|
@ -75,13 +70,5 @@ describe('WebhookServer', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const mockResponse = (data = {}, headers = {}, status = 200) => {
|
|
||||||
const response = mock<IWebhookResponseCallbackData>();
|
|
||||||
response.responseCode = status;
|
|
||||||
response.data = data;
|
|
||||||
response.headers = headers;
|
|
||||||
return response;
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -23,6 +23,12 @@ import type { Readable } from 'stream';
|
||||||
|
|
||||||
import { formatPrivateKey, generatePairedItemData } from '../../utils/utilities';
|
import { formatPrivateKey, generatePairedItemData } from '../../utils/utilities';
|
||||||
|
|
||||||
|
type Options = {
|
||||||
|
responseHeaders: { entries: Array<{ name: string; value: string }> };
|
||||||
|
responseCode: number;
|
||||||
|
responseKey: string;
|
||||||
|
};
|
||||||
|
|
||||||
export class RespondToWebhook implements INodeType {
|
export class RespondToWebhook implements INodeType {
|
||||||
description: INodeTypeDescription = {
|
description: INodeTypeDescription = {
|
||||||
displayName: 'Respond to Webhook',
|
displayName: 'Respond to Webhook',
|
||||||
|
@ -323,19 +329,16 @@ export class RespondToWebhook implements INodeType {
|
||||||
}
|
}
|
||||||
|
|
||||||
const respondWith = this.getNodeParameter('respondWith', 0) as string;
|
const respondWith = this.getNodeParameter('respondWith', 0) as string;
|
||||||
const options = this.getNodeParameter('options', 0, {});
|
const options = this.getNodeParameter('options', 0, {}) as Options;
|
||||||
|
|
||||||
const headers = {} as IDataObject;
|
const headers: IN8nHttpFullResponse['headers'] = {};
|
||||||
if (options.responseHeaders) {
|
if (options.responseHeaders?.entries?.length) {
|
||||||
for (const header of (options.responseHeaders as IDataObject).entries as IDataObject[]) {
|
for (const header of options.responseHeaders.entries) {
|
||||||
if (typeof header.name !== 'string') {
|
headers[String(header.name).toLowerCase()] = String(header.value);
|
||||||
header.name = header.name?.toString();
|
|
||||||
}
|
|
||||||
headers[header.name?.toLowerCase() as string] = header.value?.toString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let statusCode = (options.responseCode as number) || 200;
|
let statusCode = options.responseCode ?? 200;
|
||||||
let responseBody: IN8nHttpResponse | Readable;
|
let responseBody: IN8nHttpResponse | Readable;
|
||||||
if (respondWith === 'json') {
|
if (respondWith === 'json') {
|
||||||
const responseBodyParameter = this.getNodeParameter('responseBody', 0) as string;
|
const responseBodyParameter = this.getNodeParameter('responseBody', 0) as string;
|
||||||
|
@ -381,11 +384,11 @@ export class RespondToWebhook implements INodeType {
|
||||||
} else if (respondWith === 'allIncomingItems') {
|
} else if (respondWith === 'allIncomingItems') {
|
||||||
const respondItems = items.map((item) => item.json);
|
const respondItems = items.map((item) => item.json);
|
||||||
responseBody = options.responseKey
|
responseBody = options.responseKey
|
||||||
? set({}, options.responseKey as string, respondItems)
|
? set({}, options.responseKey, respondItems)
|
||||||
: respondItems;
|
: respondItems;
|
||||||
} else if (respondWith === 'firstIncomingItem') {
|
} else if (respondWith === 'firstIncomingItem') {
|
||||||
responseBody = options.responseKey
|
responseBody = options.responseKey
|
||||||
? set({}, options.responseKey as string, items[0].json)
|
? set({}, options.responseKey, items[0].json)
|
||||||
: items[0].json;
|
: items[0].json;
|
||||||
} else if (respondWith === 'text') {
|
} else if (respondWith === 'text') {
|
||||||
responseBody = this.getNodeParameter('responseBody', 0) as string;
|
responseBody = this.getNodeParameter('responseBody', 0) as string;
|
||||||
|
@ -418,7 +421,7 @@ export class RespondToWebhook implements INodeType {
|
||||||
responseBody = { binaryData };
|
responseBody = { binaryData };
|
||||||
} else {
|
} else {
|
||||||
responseBody = Buffer.from(binaryData.data, BINARY_ENCODING);
|
responseBody = Buffer.from(binaryData.data, BINARY_ENCODING);
|
||||||
headers['content-length'] = (responseBody as Buffer).length;
|
headers['content-length'] = String((responseBody as Buffer).length);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!headers['content-type']) {
|
if (!headers['content-type']) {
|
||||||
|
@ -426,7 +429,7 @@ export class RespondToWebhook implements INodeType {
|
||||||
}
|
}
|
||||||
} else if (respondWith === 'redirect') {
|
} else if (respondWith === 'redirect') {
|
||||||
headers.location = this.getNodeParameter('redirectURL', 0) as string;
|
headers.location = this.getNodeParameter('redirectURL', 0) as string;
|
||||||
statusCode = (options.responseCode as number) ?? 307;
|
statusCode = options.responseCode ?? 307;
|
||||||
} else if (respondWith !== 'noData') {
|
} else if (respondWith !== 'noData') {
|
||||||
throw new NodeOperationError(
|
throw new NodeOperationError(
|
||||||
this.getNode(),
|
this.getNode(),
|
||||||
|
|
|
@ -518,10 +518,12 @@ export interface PaginationOptions {
|
||||||
|
|
||||||
export type IN8nHttpResponse = IDataObject | Buffer | GenericValue | GenericValue[] | null;
|
export type IN8nHttpResponse = IDataObject | Buffer | GenericValue | GenericValue[] | null;
|
||||||
|
|
||||||
|
type ResponseHeaders = Record<string, string>;
|
||||||
|
|
||||||
export interface IN8nHttpFullResponse {
|
export interface IN8nHttpFullResponse {
|
||||||
body: IN8nHttpResponse | Readable;
|
body: IN8nHttpResponse | Readable;
|
||||||
__bodyResolved?: boolean;
|
__bodyResolved?: boolean;
|
||||||
headers: IDataObject;
|
headers: ResponseHeaders;
|
||||||
statusCode: number;
|
statusCode: number;
|
||||||
statusMessage?: string;
|
statusMessage?: string;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue