feat(webhook): add webhook application

This commit is contained in:
Ali BARIN
2022-12-07 23:41:46 +01:00
parent 67964192de
commit 3c62f182ab
13 changed files with 107 additions and 20 deletions

View File

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 48 48" width="48px" height="48px"><g id="surface56721297">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,12.156863%,32.156864%);fill-opacity:1;" d="M 35 37 C 32.800781 37 31 35.199219 31 33 C 31 30.800781 32.800781 29 35 29 C 37.199219 29 39 30.800781 39 33 C 39 35.199219 37.199219 37 35 37 Z M 35 37 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,12.156863%,32.156864%);fill-opacity:1;" d="M 35 43 C 32 43 29.101562 41.601562 27.199219 39.300781 L 30.300781 36.800781 C 31.398438 38.199219 33.199219 39.101562 35 39.101562 C 38.300781 39.101562 41 36.398438 41 33.101562 C 41 29.800781 38.300781 27.101562 35 27.101562 C 34 27.101562 33 27.398438 32.101562 27.800781 L 30.398438 28.800781 L 23.300781 16 L 26.800781 14.101562 L 32.101562 23.5 C 33.101562 23.199219 34.101562 23 35.101562 23 C 40.601562 23 45.101562 27.5 45.101562 33 C 45.101562 38.5 40.5 43 35 43 Z M 35 43 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,12.156863%,32.156864%);fill-opacity:1;" d="M 14 43 C 8.5 43 4 38.5 4 33 C 4 28.398438 7.101562 24.5 11.5 23.300781 L 12.5 27.199219 C 9.898438 27.898438 8 30.300781 8 33 C 8 36.300781 10.699219 39 14 39 C 17.300781 39 20 36.300781 20 33 L 20 31 L 35 31 L 35 35 L 23.800781 35 C 22.898438 39.601562 18.800781 43 14 43 Z M 14 43 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,34.901962%,96.862745%);fill-opacity:1;" d="M 14 37 C 11.800781 37 10 35.199219 10 33 C 10 30.800781 11.800781 29 14 29 C 16.199219 29 18 30.800781 18 33 C 18 35.199219 16.199219 37 14 37 Z M 14 37 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,12.156863%,32.156864%);fill-opacity:1;" d="M 25 19 C 22.800781 19 21 17.199219 21 15 C 21 12.800781 22.800781 11 25 11 C 27.199219 11 29 12.800781 29 15 C 29 17.199219 27.199219 19 25 19 Z M 25 19 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,34.901962%,96.862745%);fill-opacity:1;" d="M 15.699219 34 L 12.300781 32 L 18.199219 22.300781 C 16.199219 20.398438 15 17.800781 15 15 C 15 9.5 19.5 5 25 5 C 30.5 5 35 9.5 35 15 C 35 15.898438 34.898438 16.699219 34.699219 17.5 L 30.800781 16.5 C 30.898438 16 31 15.5 31 15 C 31 11.699219 28.300781 9 25 9 C 21.699219 9 19 11.699219 19 15 C 19 17.101562 20.101562 19 21.898438 20.101562 L 23.601562 21.101562 Z M 15.699219 34 "/>
</g></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,5 @@
import verifyWebhook from './verify-webhook';
export default {
verifyWebhook,
};

View File

@@ -0,0 +1,7 @@
import { IGlobalVariable } from '@automatisch/types';
const verifyWebhook = async ($: IGlobalVariable) => {
return true;
};
export default verifyWebhook;

View File

View File

@@ -0,0 +1,16 @@
import defineApp from '../../helpers/define-app';
import auth from './auth';
import triggers from './triggers';
export default defineApp({
name: 'Webhook',
key: 'webhook',
iconUrl: '{BASE_URL}/apps/webhook/assets/favicon.svg',
authDocUrl: 'https://automatisch.io/docs/apps/webhook/connection',
supportsConnections: false,
baseUrl: '',
apiBaseUrl: '',
primaryColor: '0059F7',
auth,
triggers,
});

View File

@@ -0,0 +1,20 @@
import isEmpty from 'lodash/isEmpty';
import defineTrigger from '../../../../helpers/define-trigger';
export default defineTrigger({
name: 'Catch raw webhook',
key: 'catchRawWebhook',
type: 'webhook',
description: 'Triggers when the webhook receives a request.',
async testRun($) {
if (!isEmpty($.lastExecutionStep?.dataOut)) {
$.pushTriggerItem({
raw: $.lastExecutionStep.dataOut,
meta: {
internalId: '',
}
});
}
},
});

View File

@@ -0,0 +1,3 @@
import catchRawWebhook from './catch-raw-webhook';
export default [catchRawWebhook];

View File

@@ -13,18 +13,19 @@ export default async (request: IRequest, response: Response) => {
.findById(request.params.flowId) .findById(request.params.flowId)
.throwIfNotFound(); .throwIfNotFound();
if (!flow.active) {
return response.send(404);
}
const triggerStep = await flow.getTriggerStep(); const triggerStep = await flow.getTriggerStep();
const triggerCommand = await triggerStep.getTriggerCommand(); const triggerCommand = await triggerStep.getTriggerCommand();
const app = await triggerStep.getApp();
const isWebhookApp = app.key === 'webhook';
if (triggerCommand.type !== 'webhook') { if (!flow.active && !isWebhookApp) {
return response.send(404); return response.sendStatus(404);
}
if (triggerCommand.type !== 'webhook') {
return response.sendStatus(404);
} }
const app = await triggerStep.getApp();
if (app.auth.verifyWebhook) { if (app.auth.verifyWebhook) {
const $ = await globalVariable({ const $ = await globalVariable({
@@ -42,8 +43,20 @@ export default async (request: IRequest, response: Response) => {
} }
} }
// in case trigger type is 'webhook'
let payload = request.body;
// in case it's our built-in generic webhook trigger
if (isWebhookApp) {
payload = {
headers: request.headers,
body: request.body,
query: request.query,
}
}
const triggerItem: ITriggerItem = { const triggerItem: ITriggerItem = {
raw: request.body, raw: payload,
meta: { meta: {
internalId: await bcrypt.hash(request.rawBody, 1), internalId: await bcrypt.hash(request.rawBody, 1),
}, },

View File

@@ -342,6 +342,7 @@ type Step {
key: String key: String
appKey: String appKey: String
iconUrl: String iconUrl: String
webhookUrl: String
type: StepEnumType type: StepEnumType
parameters: JSONObject parameters: JSONObject
connection: Connection connection: Connection

View File

@@ -77,6 +77,7 @@ const globalVariable = async (
id: execution?.id, id: execution?.id,
testRun, testRun,
}, },
lastExecutionStep: (await step?.getLastExecutionStep())?.toJSON(),
triggerOutput: { triggerOutput: {
data: [], data: [],
}, },

View File

@@ -1,10 +1,11 @@
import { URL } from 'node:url';
import { QueryContext, ModelOptions } from 'objection'; import { QueryContext, ModelOptions } from 'objection';
import type { IJSONObject, IStep } from '@automatisch/types';
import Base from './base'; import Base from './base';
import App from './app'; import App from './app';
import Flow from './flow'; import Flow from './flow';
import Connection from './connection'; import Connection from './connection';
import ExecutionStep from './execution-step'; import ExecutionStep from './execution-step';
import type { IJSONObject, IStep } from '@automatisch/types';
import Telemetry from '../helpers/telemetry'; import Telemetry from '../helpers/telemetry';
import appConfig from '../config/app'; import appConfig from '../config/app';
@@ -46,7 +47,7 @@ class Step extends Base {
}; };
static get virtualAttributes() { static get virtualAttributes() {
return ['iconUrl']; return ['iconUrl', 'webhookUrl'];
} }
static relationMappings = () => ({ static relationMappings = () => ({
@@ -82,6 +83,13 @@ class Step extends Base {
return `${appConfig.baseUrl}/apps/${this.appKey}/assets/favicon.svg`; return `${appConfig.baseUrl}/apps/${this.appKey}/assets/favicon.svg`;
} }
get webhookUrl() {
if (this.appKey !== 'webhook') return null;
const url = new URL(`/webhooks/${this.flowId}`, appConfig.webhookUrl);
return url.toString();
}
async $afterInsert(queryContext: QueryContext) { async $afterInsert(queryContext: QueryContext) {
await super.$afterInsert(queryContext); await super.$afterInsert(queryContext);
Telemetry.stepCreated(this); Telemetry.stepCreated(this);
@@ -106,6 +114,14 @@ class Step extends Base {
return await App.findOneByKey(this.appKey); return await App.findOneByKey(this.appKey);
} }
async getLastExecutionStep() {
const lastExecutionStep = await this.$relatedQuery('executionSteps')
.orderBy('created_at', 'desc')
.first();
return lastExecutionStep;
}
async getNextStep() { async getNextStep() {
const flow = await this.$relatedQuery('flow'); const flow = await this.$relatedQuery('flow');

View File

@@ -3,6 +3,8 @@ import webhookHandler from '../controllers/webhooks/handler';
const router = Router(); const router = Router();
router.get('/:flowId', webhookHandler);
router.put('/:flowId', webhookHandler);
router.post('/:flowId', webhookHandler); router.post('/:flowId', webhookHandler);
export default router; export default router;

View File

@@ -54,6 +54,7 @@ export interface IStep {
key?: string; key?: string;
appKey?: string; appKey?: string;
iconUrl: string; iconUrl: string;
webhookUrl: string;
type: 'action' | 'trigger'; type: 'action' | 'trigger';
connectionId?: string; connectionId?: string;
status: string; status: string;
@@ -180,23 +181,16 @@ export interface IDynamicData {
export interface IAuth { export interface IAuth {
generateAuthUrl?($: IGlobalVariable): Promise<void>; generateAuthUrl?($: IGlobalVariable): Promise<void>;
verifyCredentials($: IGlobalVariable): Promise<void>; verifyCredentials?($: IGlobalVariable): Promise<void>;
isStillVerified($: IGlobalVariable): Promise<boolean>; isStillVerified?($: IGlobalVariable): Promise<boolean>;
refreshToken?($: IGlobalVariable): Promise<void>; refreshToken?($: IGlobalVariable): Promise<void>;
verifyWebhook?($: IGlobalVariable): Promise<boolean>; verifyWebhook?($: IGlobalVariable): Promise<boolean>;
isRefreshTokenRequested?: boolean; isRefreshTokenRequested?: boolean;
fields: IField[]; fields?: IField[];
authenticationSteps?: IAuthenticationStep[]; authenticationSteps?: IAuthenticationStep[];
reconnectionSteps?: IAuthenticationStep[]; reconnectionSteps?: IAuthenticationStep[];
} }
export interface IService {
authenticationClient?: IAuthentication;
triggers?: any;
actions?: any;
data?: any;
}
export interface ITriggerOutput { export interface ITriggerOutput {
data: ITriggerItem[]; data: ITriggerItem[];
error?: IJSONObject; error?: IJSONObject;
@@ -300,6 +294,7 @@ export type IGlobalVariable = {
id: string; id: string;
testRun: boolean; testRun: boolean;
}; };
lastExecutionStep?: IExecutionStep;
webhookUrl?: string; webhookUrl?: string;
triggerOutput?: ITriggerOutput; triggerOutput?: ITriggerOutput;
actionOutput?: IActionOutput; actionOutput?: IActionOutput;