feat(webhook): add webhook application
This commit is contained in:
8
packages/backend/src/apps/webhook/assets/favicon.svg
Normal file
8
packages/backend/src/apps/webhook/assets/favicon.svg
Normal 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 |
5
packages/backend/src/apps/webhook/auth/index.ts
Normal file
5
packages/backend/src/apps/webhook/auth/index.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import verifyWebhook from './verify-webhook';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
verifyWebhook,
|
||||||
|
};
|
7
packages/backend/src/apps/webhook/auth/verify-webhook.ts
Normal file
7
packages/backend/src/apps/webhook/auth/verify-webhook.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
|
|
||||||
|
const verifyWebhook = async ($: IGlobalVariable) => {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default verifyWebhook;
|
0
packages/backend/src/apps/webhook/index.d.ts
vendored
Normal file
0
packages/backend/src/apps/webhook/index.d.ts
vendored
Normal file
16
packages/backend/src/apps/webhook/index.ts
Normal file
16
packages/backend/src/apps/webhook/index.ts
Normal 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,
|
||||||
|
});
|
@@ -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: '',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
3
packages/backend/src/apps/webhook/triggers/index.ts
Normal file
3
packages/backend/src/apps/webhook/triggers/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import catchRawWebhook from './catch-raw-webhook';
|
||||||
|
|
||||||
|
export default [catchRawWebhook];
|
@@ -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),
|
||||||
},
|
},
|
||||||
|
@@ -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
|
||||||
|
@@ -77,6 +77,7 @@ const globalVariable = async (
|
|||||||
id: execution?.id,
|
id: execution?.id,
|
||||||
testRun,
|
testRun,
|
||||||
},
|
},
|
||||||
|
lastExecutionStep: (await step?.getLastExecutionStep())?.toJSON(),
|
||||||
triggerOutput: {
|
triggerOutput: {
|
||||||
data: [],
|
data: [],
|
||||||
},
|
},
|
||||||
|
@@ -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');
|
||||||
|
|
||||||
|
@@ -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;
|
||||||
|
15
packages/types/index.d.ts
vendored
15
packages/types/index.d.ts
vendored
@@ -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;
|
||||||
|
Reference in New Issue
Block a user