Merge pull request #759 from automatisch/feature/typeform-integration

Add new entry trigger for typeform with webhook support
This commit is contained in:
Ömer Faruk Aydın
2022-11-30 22:06:07 +01:00
committed by GitHub
26 changed files with 534 additions and 31 deletions

View File

@@ -2,6 +2,7 @@ HOST=localhost
PROTOCOL=http PROTOCOL=http
PORT=3000 PORT=3000
WEB_APP_URL=http://localhost:3001 WEB_APP_URL=http://localhost:3001
WEBHOOK_URL=http://localhost:3000
APP_ENV=development APP_ENV=development
POSTGRES_DATABASE=automatisch_development POSTGRES_DATABASE=automatisch_development
POSTGRES_PORT=5432 POSTGRES_PORT=5432

View File

@@ -1,8 +1,7 @@
import createError from 'http-errors'; import createError from 'http-errors';
import express, { Request, Response, NextFunction } from 'express'; import express from 'express';
import cors from 'cors'; import cors from 'cors';
import corsOptions from './config/cors-options'; import corsOptions from './config/cors-options';
import graphQLInstance from './helpers/graphql-instance';
import morgan from './helpers/morgan'; import morgan from './helpers/morgan';
import appAssetsHandler from './helpers/app-assets-handler'; import appAssetsHandler from './helpers/app-assets-handler';
import webUIHandler from './helpers/web-ui-handler'; import webUIHandler from './helpers/web-ui-handler';
@@ -13,6 +12,8 @@ import {
serverAdapter, serverAdapter,
} from './helpers/create-bull-board-handler'; } from './helpers/create-bull-board-handler';
import injectBullBoardHandler from './helpers/inject-bull-board-handler'; import injectBullBoardHandler from './helpers/inject-bull-board-handler';
import router from './routes';
import { IRequest } from '@automatisch/types';
createBullBoardHandler(serverAdapter); createBullBoardHandler(serverAdapter);
@@ -23,15 +24,21 @@ injectBullBoardHandler(app, serverAdapter);
appAssetsHandler(app); appAssetsHandler(app);
app.use(morgan); app.use(morgan);
app.use(express.json()); app.use(
express.json({
verify: (req, res, buf) => {
(req as IRequest).rawBody = buf;
},
})
);
app.use(express.urlencoded({ extended: false })); app.use(express.urlencoded({ extended: false }));
app.use(cors(corsOptions)); app.use(cors(corsOptions));
app.use('/graphql', graphQLInstance); app.use('/', router);
webUIHandler(app); webUIHandler(app);
// catch 404 and forward to error handler // catch 404 and forward to error handler
app.use(function (req: Request, res: Response, next: NextFunction) { app.use(function (req, res, next) {
next(createError(404)); next(createError(404));
}); });

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 245 55" width="100">
<title>Typeform</title>
<path d="M158.248 38.0664C153.148 38.0664 150.529 33.8672 150.529 28.7095C150.529 23.5519 153.102 19.5809 158.248 19.5809C163.486 19.5809 165.968 23.7344 165.968 28.7095C165.922 33.9585 163.303 38.0664 158.248 38.0664ZM108.348 19.5809C111.334 19.5809 112.575 21.3154 112.575 22.8216C112.575 26.473 108.853 28.0705 101.088 28.2988C101.088 23.7801 103.661 19.5809 108.348 19.5809ZM75.2644 38.0664C70.4398 38.0664 68.418 34.1411 68.418 28.7095C68.418 23.3237 70.4857 19.5809 75.2644 19.5809C80.135 19.5809 82.4325 23.4606 82.4325 28.7095C82.4325 34.1867 80.0431 38.0664 75.2644 38.0664ZM34.4617 14.2407H26.9261L39.4242 42.9046C37.0349 48.1992 35.7483 49.5228 34.0941 49.5228C32.3481 49.5228 30.6939 48.1079 29.4992 46.8755L26.1909 51.2573C28.4424 53.6307 31.521 55 34.6915 55C38.6431 55 41.6757 52.7178 43.4218 48.61L57.8498 14.195H50.452L42.9164 34.7344L34.4617 14.2407ZM234.064 19.5809C237.74 19.5809 238.475 22.0913 238.475 27.9793V43.4523H245V23.917C245 16.888 240.175 13.8299 235.718 13.8299C231.767 13.8299 228.137 16.2946 226.115 20.8589C224.966 16.4772 221.29 13.8299 217.017 13.8299C213.295 13.8299 209.573 16.2033 207.552 20.6307V14.2407H201.027V43.4066H207.552V30.9461C207.552 24.6473 210.86 19.5809 215.363 19.5809C219.039 19.5809 219.728 22.0913 219.728 27.9793V43.4523H226.253L226.207 30.9461C226.207 24.6473 229.561 19.5809 234.064 19.5809ZM182 14.2407H178.696V43.4066H185.22V32.2241C185.22 25.1494 188.345 20.3568 192.986 20.3568C194.18 20.3568 195.191 20.4025 196.294 20.8589L197.305 13.9668C196.478 13.8755 195.743 13.7842 195.007 13.7842C190.413 13.7842 187.104 16.9336 185.174 20.9959V14.2407H182ZM158.248 13.7842C149.61 13.7842 143.774 20.3568 143.774 28.6639C143.774 37.2905 149.702 43.7261 158.248 43.7261C166.933 43.7261 172.86 37.1079 172.86 28.6639C172.814 20.3112 166.795 13.7842 158.248 13.7842ZM108.715 38.0664C105.315 38.0664 102.788 36.332 101.731 32.8174C110.369 32.4523 118.824 30.3527 118.824 22.7303C118.824 18.3485 114.505 13.8299 108.302 13.8299C99.939 13.8299 94.2873 20.6307 94.2873 28.7095C94.2873 37.1535 99.8471 43.7718 108.164 43.7718C113.953 43.7718 117.629 41.444 120.524 38.0664L117.354 33.7759C113.999 37.1992 111.932 38.0664 108.715 38.0664ZM76.551 13.7842C73.2427 13.7842 69.9344 15.7012 68.6478 18.7137V14.2407H62.123V54.8631H68.6478V40.3485C70.0263 42.4481 73.1967 43.8174 75.9996 43.8174C84.684 43.8174 89.2789 37.3817 89.2789 28.7095C89.2329 20.1286 84.7299 13.7842 76.551 13.7842ZM30.7858 2.55602H0V8.90042H12.0386V43.4066H18.885V8.90042H30.7858V2.55602ZM127.784 14.2407H123.878V20.083H127.784V43.4066H134.309V20.083H140.65V14.2407H134.309V9.67635C134.309 6.75519 135.504 5.75104 138.215 5.75104C139.225 5.75104 140.144 6.0249 141.385 6.43568L142.855 0.958506C141.661 0.273859 139.271 0 137.709 0C131.46 0 127.784 3.74274 127.784 10.2241V14.2407Z" fill="#262627"/>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -0,0 +1,21 @@
import { IField, IGlobalVariable } from '@automatisch/types';
import { URLSearchParams } from 'url';
import authScope from '../common/auth-scope';
export default async function generateAuthUrl($: IGlobalVariable) {
const oauthRedirectUrl = $.app.auth.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl'
).value;
const searchParams = new URLSearchParams({
client_id: $.auth.data.clientId as string,
redirect_uri: oauthRedirectUrl as string,
scope: authScope.join(' '),
});
const url = `${$.app.apiBaseUrl}/oauth/authorize?${searchParams.toString()}`;
await $.auth.set({
url,
});
}

View File

@@ -0,0 +1,50 @@
import generateAuthUrl from './generate-auth-url';
import verifyCredentials from './verify-credentials';
import isStillVerified from './is-still-verified';
import refreshToken from './refresh-token';
import verifyWebhook from './verify-webhook';
export default {
fields: [
{
key: 'oAuthRedirectUrl',
label: 'OAuth Redirect URL',
type: 'string' as const,
required: true,
readOnly: true,
value: '{WEB_APP_URL}/app/typeform/connections/add',
placeholder: null,
description:
'When asked to input an OAuth callback or redirect URL in Typeform OAuth, enter the URL above.',
clickToCopy: true,
},
{
key: 'clientId',
label: 'Client ID',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
{
key: 'clientSecret',
label: 'Client Secret',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
],
generateAuthUrl,
verifyCredentials,
isStillVerified,
refreshToken,
verifyWebhook,
};

View File

@@ -0,0 +1,9 @@
import { IGlobalVariable } from '@automatisch/types';
const isStillVerified = async ($: IGlobalVariable) => {
await $.http.get('/me');
return true;
};
export default isStillVerified;

View File

@@ -0,0 +1,24 @@
import { IGlobalVariable } from '@automatisch/types';
import { URLSearchParams } from 'url';
import authScope from '../common/auth-scope';
const refreshToken = async ($: IGlobalVariable) => {
const params = new URLSearchParams({
grant_type: 'refresh_token',
client_id: $.auth.data.clientId as string,
client_secret: $.auth.data.clientSecret as string,
refresh_token: $.auth.data.refreshToken as string,
scope: authScope.join(' '),
});
const { data } = await $.http.post('/oauth/token', params.toString());
await $.auth.set({
accessToken: data.access_token,
expiresIn: data.expires_in,
tokenType: data.token_type,
refreshToken: data.refresh_token,
});
};
export default refreshToken;

View File

@@ -0,0 +1,46 @@
import { IField, IGlobalVariable } from '@automatisch/types';
import { URLSearchParams } from 'url';
const verifyCredentials = async ($: IGlobalVariable) => {
const oauthRedirectUrl = $.app.auth.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl'
).value;
const params = new URLSearchParams({
grant_type: 'authorization_code',
code: $.auth.data.code as string,
client_id: $.auth.data.clientId as string,
client_secret: $.auth.data.clientSecret as string,
redirect_uri: oauthRedirectUrl as string,
});
const { data: verifiedCredentials } = await $.http.post(
'/oauth/token',
params.toString()
);
const {
access_token: accessToken,
expires_in: expiresIn,
token_type: tokenType,
refresh_token: refreshToken,
} = verifiedCredentials;
const { data: user } = await $.http.get('/me', {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
await $.auth.set({
accessToken,
expiresIn,
tokenType,
userId: user.user_id,
screenName: user.alias,
email: user.email,
refreshToken,
});
};
export default verifyCredentials;

View File

@@ -0,0 +1,20 @@
import crypto from 'crypto';
import { IGlobalVariable } from '@automatisch/types';
import appConfig from '../../../config/app';
const verifyWebhook = async ($: IGlobalVariable) => {
const signature = $.request.headers['typeform-signature'] as string;
const isValid = verifySignature(signature, $.request.rawBody.toString());
return isValid;
};
const verifySignature = function (receivedSignature: string, payload: string) {
const hash = crypto
.createHmac('sha256', appConfig.appSecretKey)
.update(payload)
.digest('base64');
return receivedSignature === `sha256=${hash}`;
};
export default verifyWebhook;

View File

@@ -0,0 +1,12 @@
import { TBeforeRequest } from '@automatisch/types';
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
if ($.auth.data?.accessToken) {
const authorizationHeader = `Bearer ${$.auth.data.accessToken}`;
requestConfig.headers.Authorization = authorizationHeader;
}
return requestConfig;
};
export default addAuthHeader;

View File

@@ -0,0 +1,12 @@
const authScope: string[] = [
'forms:read',
'forms:write',
'webhooks:read',
'webhooks:write',
'responses:read',
'accounts:read',
'workspaces:read',
'offline',
];
export default authScope;

View File

@@ -0,0 +1,3 @@
import listForms from './list-forms';
export default [listForms];

View File

@@ -0,0 +1,25 @@
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
export default {
name: 'List forms',
key: 'listForms',
async run($: IGlobalVariable) {
const forms: {
data: IJSONObject[];
} = {
data: [],
};
const response = await $.http.get('/forms');
forms.data = response.data.items.map((form: IJSONObject) => {
return {
value: form.id,
name: form.title,
};
});
return forms;
},
};

View File

View File

@@ -0,0 +1,20 @@
import defineApp from '../../helpers/define-app';
import addAuthHeader from './common/add-auth-header';
import auth from './auth';
import triggers from './triggers';
import dynamicData from './dynamic-data';
export default defineApp({
name: 'Typeform',
key: 'typeform',
iconUrl: '{BASE_URL}/apps/typeform/assets/favicon.svg',
authDocUrl: 'https://automatisch.io/docs/apps/typeform/connection',
supportsConnections: true,
baseUrl: 'https://typeform.com',
apiBaseUrl: 'https://api.typeform.com',
primaryColor: '262627',
beforeRequest: [addAuthHeader],
auth,
triggers,
dynamicData,
});

View File

@@ -0,0 +1,3 @@
import newEntry from './new-entry';
export default [newEntry];

View File

@@ -0,0 +1,89 @@
import appConfig from '../../../../config/app';
import defineTrigger from '../../../../helpers/define-trigger';
export default defineTrigger({
name: 'New entry',
key: 'newEntry',
type: 'webhook',
description: 'Triggers when a new form submitted.',
arguments: [
{
label: 'Form',
key: 'formId',
type: 'dropdown' as const,
required: true,
description: 'Pick a form to receive submissions.',
variables: false,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listForms',
},
],
},
},
],
async testRun($) {
const { data: form } = await $.http.get(
`/forms/${$.step.parameters.formId}`
);
const { data: responses } = await $.http.get(
`/forms/${$.step.parameters.formId}/responses`
);
const lastResponse = responses.items[0];
if (!lastResponse) {
return;
}
const computedWebhookEvent = {
event_type: 'form_response',
form_response: {
form_id: form.id,
token: lastResponse.token,
landed_at: lastResponse.landed_at,
submitted_at: lastResponse.submitted_at,
definition: {
id: $.step.parameters.formId,
title: form.title,
fields: form?.fields,
},
answers: lastResponse.answers,
},
};
const dataItem = {
raw: computedWebhookEvent,
meta: {
internalId: computedWebhookEvent.form_response.token,
},
};
$.pushTriggerItem(dataItem);
},
async registerHook($) {
const subscriptionPayload = {
enabled: true,
url: $.webhookUrl,
secret: appConfig.appSecretKey,
};
await $.http.put(
`/forms/${$.step.parameters.formId}/webhooks/${$.flow.id}`,
subscriptionPayload
);
},
async unregisterHook($) {
await $.http.delete(
`/forms/${$.step.parameters.formId}/webhooks/${$.flow.id}`
);
},
});

View File

@@ -6,6 +6,7 @@ type AppConfig = {
protocol: string; protocol: string;
port: string; port: string;
webAppUrl: string; webAppUrl: string;
webhookUrl: string;
appEnv: string; appEnv: string;
isDev: boolean; isDev: boolean;
postgresDatabase: string; postgresDatabase: string;
@@ -37,6 +38,8 @@ const serveWebAppSeparately =
process.env.SERVE_WEB_APP_SEPARATELY === 'true' ? true : false; process.env.SERVE_WEB_APP_SEPARATELY === 'true' ? true : false;
let webAppUrl = `${protocol}://${host}:${port}`; let webAppUrl = `${protocol}://${host}:${port}`;
const webhookUrl = process.env.WEBHOOK_URL || webAppUrl;
if (serveWebAppSeparately) { if (serveWebAppSeparately) {
webAppUrl = process.env.WEB_APP_URL || 'http://localhost:3001'; webAppUrl = process.env.WEB_APP_URL || 'http://localhost:3001';
} }
@@ -73,6 +76,7 @@ const appConfig: AppConfig = {
bullMQDashboardPassword: process.env.BULLMQ_DASHBOARD_PASSWORD, bullMQDashboardPassword: process.env.BULLMQ_DASHBOARD_PASSWORD,
baseUrl, baseUrl,
webAppUrl, webAppUrl,
webhookUrl,
telemetryEnabled: process.env.TELEMETRY_ENABLED === 'false' ? false : true, telemetryEnabled: process.env.TELEMETRY_ENABLED === 'false' ? false : true,
}; };

View File

@@ -0,0 +1,75 @@
import { Response } from 'express';
import bcrypt from 'bcrypt';
import { IRequest, ITriggerItem } from '@automatisch/types';
import Flow from '../../models/flow';
import { processTrigger } from '../../services/trigger';
import actionQueue from '../../queues/action';
import globalVariable from '../../helpers/global-variable';
import { REMOVE_AFTER_30_DAYS_OR_150_JOBS, REMOVE_AFTER_7_DAYS_OR_50_JOBS } from '../../helpers/remove-job-configuration';
export default async (request: IRequest, response: Response) => {
const flow = await Flow.query()
.findById(request.params.flowId)
.throwIfNotFound();
if (!flow.active) {
return response.send(404);
}
const triggerStep = await flow.getTriggerStep();
const triggerCommand = await triggerStep.getTriggerCommand();
if (triggerCommand.type !== 'webhook') {
return response.send(404);
}
const app = await triggerStep.getApp();
if (app.auth.verifyWebhook) {
const $ = await globalVariable({
flow,
connection: await triggerStep.$relatedQuery('connection'),
app,
step: triggerStep,
request,
});
const verified = await app.auth.verifyWebhook($);
if (!verified) {
return response.sendStatus(401);
}
}
const triggerItem: ITriggerItem = {
raw: request.body,
meta: {
internalId: await bcrypt.hash(request.rawBody, 1),
},
};
const { flowId, executionId } = await processTrigger({
flowId: flow.id,
stepId: triggerStep.id,
triggerItem,
});
const nextStep = await triggerStep.getNextStep();
const jobName = `${executionId}-${nextStep.id}`;
const jobPayload = {
flowId,
executionId,
stepId: nextStep.id,
};
const jobOptions = {
removeOnComplete: REMOVE_AFTER_7_DAYS_OR_50_JOBS,
removeOnFail: REMOVE_AFTER_30_DAYS_OR_150_JOBS,
}
await actionQueue.add(jobName, jobPayload, jobOptions);
return response.sendStatus(200);
};

View File

@@ -1,6 +1,7 @@
import Context from '../../types/express/context'; import Context from '../../types/express/context';
import flowQueue from '../../queues/flow'; import flowQueue from '../../queues/flow';
import { REMOVE_AFTER_30_DAYS_OR_150_JOBS, REMOVE_AFTER_7_DAYS_OR_50_JOBS } from '../../helpers/remove-job-configuration'; import { REMOVE_AFTER_30_DAYS_OR_150_JOBS, REMOVE_AFTER_7_DAYS_OR_50_JOBS } from '../../helpers/remove-job-configuration';
import globalVariable from '../../helpers/global-variable';
type Params = { type Params = {
input: { input: {
@@ -39,6 +40,21 @@ const updateFlowStatus = async (
pattern: interval || EVERY_15_MINUTES_CRON, pattern: interval || EVERY_15_MINUTES_CRON,
}; };
if (trigger.type === 'webhook') {
const $ = await globalVariable({
flow,
connection: await triggerStep.$relatedQuery('connection'),
app: await triggerStep.getApp(),
step: triggerStep,
testRun: false,
});
if (flow.active) {
await trigger.registerHook($);
} else {
await trigger.unregisterHook($);
}
} else {
if (flow.active) { if (flow.active) {
flow = await flow.$query().patchAndFetch({ flow = await flow.$query().patchAndFetch({
published_at: new Date().toISOString(), published_at: new Date().toISOString(),
@@ -62,6 +78,7 @@ const updateFlowStatus = async (
await flowQueue.removeRepeatableByKey(job.key); await flowQueue.removeRepeatableByKey(job.key);
} }
}
return flow; return flow;
}; };

View File

@@ -3,12 +3,14 @@ import Connection from '../models/connection';
import Flow from '../models/flow'; import Flow from '../models/flow';
import Step from '../models/step'; import Step from '../models/step';
import Execution from '../models/execution'; import Execution from '../models/execution';
import appConfig from '../config/app';
import { import {
IJSONObject, IJSONObject,
IApp, IApp,
IGlobalVariable, IGlobalVariable,
ITriggerItem, ITriggerItem,
IActionItem, IActionItem,
IRequest,
} from '@automatisch/types'; } from '@automatisch/types';
import EarlyExitError from '../errors/early-exit'; import EarlyExitError from '../errors/early-exit';
@@ -19,12 +21,21 @@ type GlobalVariableOptions = {
step?: Step; step?: Step;
execution?: Execution; execution?: Execution;
testRun?: boolean; testRun?: boolean;
request?: IRequest;
}; };
const globalVariable = async ( const globalVariable = async (
options: GlobalVariableOptions options: GlobalVariableOptions
): Promise<IGlobalVariable> => { ): Promise<IGlobalVariable> => {
const { connection, app, flow, step, execution, testRun = false } = options; const {
connection,
app,
flow,
step,
execution,
request,
testRun = false,
} = options;
const lastInternalId = testRun ? undefined : await flow?.lastInternalId(); const lastInternalId = testRun ? undefined : await flow?.lastInternalId();
const nextStep = await step?.getNextStep(); const nextStep = await step?.getNextStep();
@@ -95,12 +106,22 @@ const globalVariable = async (
}, },
}; };
if (request) {
$.request = request;
}
$.http = createHttpClient({ $.http = createHttpClient({
$, $,
baseURL: app.apiBaseUrl, baseURL: app.apiBaseUrl,
beforeRequest: app.beforeRequest, beforeRequest: app.beforeRequest,
}); });
if (flow) {
const webhookUrl = appConfig.webhookUrl + '/webhooks/' + flow.id;
$.webhookUrl = webhookUrl;
}
const lastInternalIds = const lastInternalIds =
testRun || (flow && step.isAction) ? [] : await flow?.lastInternalIds(2000); testRun || (flow && step.isAction) ? [] : await flow?.lastInternalIds(2000);

View File

@@ -1,12 +1,13 @@
import { graphqlHTTP } from 'express-graphql';
import logger from '../helpers/logger';
import { applyMiddleware } from 'graphql-middleware';
import authentication from '../helpers/authentication';
import { join } from 'path'; import { join } from 'path';
import { graphqlHTTP } from 'express-graphql';
import { loadSchemaSync } from '@graphql-tools/load'; import { loadSchemaSync } from '@graphql-tools/load';
import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader'; import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader';
import { addResolversToSchema } from '@graphql-tools/schema'; import { addResolversToSchema } from '@graphql-tools/schema';
import { applyMiddleware } from 'graphql-middleware';
import logger from '../helpers/logger';
import authentication from '../helpers/authentication';
import resolvers from '../graphql/resolvers'; import resolvers from '../graphql/resolvers';
import HttpError from '../errors/http';
const schema = loadSchemaSync(join(__dirname, '../graphql/schema.graphql'), { const schema = loadSchemaSync(join(__dirname, '../graphql/schema.graphql'), {
loaders: [new GraphQLFileLoader()], loaders: [new GraphQLFileLoader()],
@@ -23,6 +24,10 @@ const graphQLInstance = graphqlHTTP({
customFormatErrorFn: (error) => { customFormatErrorFn: (error) => {
logger.error(error.path + ' : ' + error.message + '\n' + error.stack); logger.error(error.path + ' : ' + error.message + '\n' + error.stack);
if (error.originalError instanceof HttpError) {
delete (error.originalError as HttpError).response;
}
return error.originalError; return error.originalError;
}, },
}); });

View File

@@ -0,0 +1,10 @@
import { Router } from 'express';
import graphQLInstance from '../helpers/graphql-instance';
import webhooksRouter from './webhooks';
const router = Router();
router.use('/graphql', graphQLInstance);
router.use('/webhooks', webhooksRouter);
export default router;

View File

@@ -0,0 +1,8 @@
import { Router } from 'express';
import webhookHandler from '../controllers/webhooks/handler';
const router = Router();
router.post('/:flowId', webhookHandler);
export default router;

View File

@@ -23,7 +23,11 @@ export const processFlow = async (options: ProcessFlowOptions) => {
}); });
try { try {
if (triggerCommand.type === 'webhook' && !flow.active) {
await triggerCommand.testRun($);
} else {
await triggerCommand.run($); await triggerCommand.run($);
}
} catch (error) { } catch (error) {
if (error instanceof EarlyExitError === false) { if (error instanceof EarlyExitError === false) {
if (error instanceof HttpError) { if (error instanceof HttpError) {

View File

@@ -1,5 +1,6 @@
import type { AxiosInstance, AxiosRequestConfig } from 'axios'; import type { AxiosInstance, AxiosRequestConfig } from 'axios';
export type IHttpClient = AxiosInstance; export type IHttpClient = AxiosInstance;
import type { Request } from 'express';
// Type definitions for automatisch // Type definitions for automatisch
@@ -182,6 +183,7 @@ export interface IAuth {
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>;
isRefreshTokenRequested?: boolean; isRefreshTokenRequested?: boolean;
fields: IField[]; fields: IField[];
authenticationSteps?: IAuthenticationStep[]; authenticationSteps?: IAuthenticationStep[];
@@ -210,10 +212,14 @@ export interface ITriggerItem {
export interface IBaseTrigger { export interface IBaseTrigger {
name: string; name: string;
key: string; key: string;
type?: 'webhook' | 'polling';
pollInterval?: number; pollInterval?: number;
description: string; description: string;
getInterval?(parameters: IStep['parameters']): string; getInterval?(parameters: IStep['parameters']): string;
run($: IGlobalVariable): Promise<void>; run?($: IGlobalVariable): Promise<void>;
testRun?($: IGlobalVariable): Promise<void>;
registerHook?($: IGlobalVariable): Promise<void>;
unregisterHook?($: IGlobalVariable): Promise<void>;
sort?(item: ITriggerItem, nextItem: ITriggerItem): number; sort?(item: ITriggerItem, nextItem: ITriggerItem): number;
} }
@@ -238,7 +244,7 @@ export interface IBaseAction {
name: string; name: string;
key: string; key: string;
description: string; description: string;
run($: IGlobalVariable): Promise<void>; run?($: IGlobalVariable): Promise<void>;
} }
export interface IRawAction extends IBaseAction { export interface IRawAction extends IBaseAction {
@@ -274,6 +280,7 @@ export type IGlobalVariable = {
}; };
app: IApp; app: IApp;
http?: IHttpClient; http?: IHttpClient;
request?: IRequest;
flow?: { flow?: {
id: string; id: string;
lastInternalId: string; lastInternalId: string;
@@ -293,6 +300,7 @@ export type IGlobalVariable = {
id: string; id: string;
testRun: boolean; testRun: boolean;
}; };
webhookUrl?: string;
triggerOutput?: ITriggerOutput; triggerOutput?: ITriggerOutput;
actionOutput?: IActionOutput; actionOutput?: IActionOutput;
pushTriggerItem?: (triggerItem: ITriggerItem) => void; pushTriggerItem?: (triggerItem: ITriggerItem) => void;
@@ -308,3 +316,8 @@ declare module 'axios' {
additionalProperties?: Record<string, unknown>; additionalProperties?: Record<string, unknown>;
} }
} }
export interface IRequest extends Request {
rawBody?: Buffer;
}