feat(vonage): Introduce Vonage API receive and send message integrations
This commit is contained in:
3
packages/backend/src/apps/vonage/actions/index.ts
Normal file
3
packages/backend/src/apps/vonage/actions/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import sendMessage from './send-message';
|
||||
|
||||
export default [sendMessage];
|
@@ -0,0 +1,82 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
export default defineAction({
|
||||
name: 'Send Message',
|
||||
key: 'sendMessage',
|
||||
description: 'Send a message to a number.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Message Type',
|
||||
key: 'messageType',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'The type of message to send. e.g. text',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Channel',
|
||||
key: 'channel',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description:
|
||||
'The channel to send the message through. e.g. sms, whatsapp',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'From Number',
|
||||
key: 'fromNumber',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description:
|
||||
'The number to send the message from. Include country code. Example: 15551234567',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'To Number',
|
||||
key: 'toNumber',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description:
|
||||
'The number to send the message to. Include country code. Example: 15551234567',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Message',
|
||||
key: 'message',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'The message to send.',
|
||||
variables: true,
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const messageType = $.step.parameters.messageType as string;
|
||||
const channel = $.step.parameters.channel as string;
|
||||
const messageBody = $.step.parameters.message as string;
|
||||
const fromNumber = ($.step.parameters.fromNumber as string).trim();
|
||||
const toNumber = ($.step.parameters.toNumber as string).trim();
|
||||
|
||||
const basicAuthToken = Buffer.from(
|
||||
`${$.auth.data.apiKey}:${$.auth.data.apiSecret}`
|
||||
).toString('base64');
|
||||
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
Authorization: `Basic ${basicAuthToken}`,
|
||||
};
|
||||
|
||||
const payload = {
|
||||
message_type: messageType,
|
||||
text: messageBody,
|
||||
to: toNumber,
|
||||
from: fromNumber,
|
||||
channel,
|
||||
};
|
||||
|
||||
const response = await $.http.post('/messages', payload, { headers });
|
||||
|
||||
$.setActionItem({ raw: response.data });
|
||||
},
|
||||
});
|
4
packages/backend/src/apps/vonage/assets/favicon.svg
Normal file
4
packages/backend/src/apps/vonage/assets/favicon.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg viewBox="0 0 230 200" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="currentColor" d="M45.3408,0 L-0.0002,0 L64.6808,146.958 C65.1748,148.081 66.7718,148.07 67.2508,146.942 L88.7628,96.337 L45.3408,0 Z"></path>
|
||||
<path fill="currentColor" d="M183.4502,0 C183.4502,0 113.9562,159.156 104.6482,173.833 C93.8292,190.896 86.6592,197.409 73.3912,199.496 C73.2682,199.515 73.1772,199.621 73.1772,199.746 C73.1772,199.886 73.2912,200 73.4312,200 L114.9552,200 C132.9432,200 145.9152,184.979 153.1042,171.714 C161.2742,156.637 229.5902,0 229.5902,0 L183.4502,0 Z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 593 B |
44
packages/backend/src/apps/vonage/auth/index.ts
Normal file
44
packages/backend/src/apps/vonage/auth/index.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import verifyCredentials from './verify-credentials';
|
||||
import isStillVerified from './is-still-verified';
|
||||
|
||||
export default {
|
||||
fields: [
|
||||
{
|
||||
key: 'screenName',
|
||||
label: 'Screen Name',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description:
|
||||
'Screen name of your connection to be used on Automatisch UI.',
|
||||
clickToCopy: false,
|
||||
},
|
||||
{
|
||||
key: 'apiKey',
|
||||
label: 'API Key',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description: 'API Key from Vonage.',
|
||||
clickToCopy: false,
|
||||
},
|
||||
{
|
||||
key: 'apiSecret',
|
||||
label: 'API Secret',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description: 'API Secret from Vonage.',
|
||||
clickToCopy: false,
|
||||
},
|
||||
],
|
||||
|
||||
verifyCredentials,
|
||||
isStillVerified,
|
||||
};
|
@@ -0,0 +1,9 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import verifyCredentials from './verify-credentials';
|
||||
|
||||
const isStillVerified = async ($: IGlobalVariable) => {
|
||||
await verifyCredentials($);
|
||||
return true;
|
||||
};
|
||||
|
||||
export default isStillVerified;
|
13
packages/backend/src/apps/vonage/auth/verify-credentials.ts
Normal file
13
packages/backend/src/apps/vonage/auth/verify-credentials.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const verifyCredentials = async ($: IGlobalVariable) => {
|
||||
await $.http.get(
|
||||
`https://rest.nexmo.com/account/get-balance?api_key=${$.auth.data.apiKey}&api_secret=${$.auth.data.apiSecret}`
|
||||
);
|
||||
|
||||
await $.auth.set({
|
||||
screenName: $.auth.data.screenName,
|
||||
});
|
||||
};
|
||||
|
||||
export default verifyCredentials;
|
0
packages/backend/src/apps/vonage/index.d.ts
vendored
Normal file
0
packages/backend/src/apps/vonage/index.d.ts
vendored
Normal file
18
packages/backend/src/apps/vonage/index.ts
Normal file
18
packages/backend/src/apps/vonage/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import defineApp from '../../helpers/define-app';
|
||||
import auth from './auth';
|
||||
import triggers from './triggers';
|
||||
import actions from './actions';
|
||||
|
||||
export default defineApp({
|
||||
name: 'Vonage',
|
||||
key: 'vonage',
|
||||
iconUrl: '{BASE_URL}/apps/vonage/assets/favicon.svg',
|
||||
authDocUrl: 'https://automatisch.io/docs/apps/vonage/connection',
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://vonage.com',
|
||||
apiBaseUrl: 'https://messages-sandbox.nexmo.com/v1',
|
||||
primaryColor: '000000',
|
||||
auth,
|
||||
triggers,
|
||||
actions,
|
||||
});
|
3
packages/backend/src/apps/vonage/triggers/index.ts
Normal file
3
packages/backend/src/apps/vonage/triggers/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import receiveMessage from './receive-message';
|
||||
|
||||
export default [receiveMessage];
|
@@ -0,0 +1,63 @@
|
||||
import defineTrigger from '../../../../helpers/define-trigger';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
|
||||
export default defineTrigger({
|
||||
name: 'Receive message - Sandbox',
|
||||
key: 'receiveMessage',
|
||||
type: 'webhook',
|
||||
description:
|
||||
'Triggers when a message is received from Vonage sandbox number. (+14157386102)',
|
||||
arguments: [
|
||||
{
|
||||
label: 'From Number',
|
||||
key: 'fromNumber',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description:
|
||||
'The number from which the message was sent. (e.g. 491234567899)',
|
||||
variables: true,
|
||||
},
|
||||
],
|
||||
|
||||
async testRun($) {
|
||||
const lastExecutionStep = await $.getLastExecutionStep();
|
||||
|
||||
if (!isEmpty(lastExecutionStep?.dataOut)) {
|
||||
$.pushTriggerItem({
|
||||
raw: lastExecutionStep.dataOut,
|
||||
meta: {
|
||||
internalId: '',
|
||||
},
|
||||
});
|
||||
} else {
|
||||
const sampleData = {
|
||||
to: '14157386102',
|
||||
from: $.step.parameters.fromNumber as string,
|
||||
text: 'Tell me how will be the weather tomorrow for Berlin?',
|
||||
channel: 'whatsapp',
|
||||
profile: {
|
||||
name: 'Sample User',
|
||||
},
|
||||
timestamp: '2023-09-18T19:52:36Z',
|
||||
message_type: 'text',
|
||||
message_uuid: '318960cc-16ff-4f66-8397-45574333a435',
|
||||
context_status: 'none',
|
||||
};
|
||||
|
||||
$.pushTriggerItem({
|
||||
raw: sampleData,
|
||||
meta: {
|
||||
internalId: '',
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
async registerHook() {
|
||||
// void
|
||||
},
|
||||
|
||||
async unregisterHook() {
|
||||
// void
|
||||
},
|
||||
});
|
34
packages/backend/src/controllers/webhooks/handler-by-app.ts
Normal file
34
packages/backend/src/controllers/webhooks/handler-by-app.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Response } from 'express';
|
||||
import { IRequest } from '@automatisch/types';
|
||||
|
||||
import Step from '../../models/step';
|
||||
import logger from '../../helpers/logger';
|
||||
import handler from '../../helpers/webhook-handler';
|
||||
|
||||
export default async (request: IRequest, response: Response) => {
|
||||
const computedRequestPayload = {
|
||||
headers: request.headers,
|
||||
body: request.body,
|
||||
query: request.query,
|
||||
params: request.params,
|
||||
};
|
||||
logger.debug(`Handling incoming webhook request at ${request.originalUrl}.`);
|
||||
logger.debug(JSON.stringify(computedRequestPayload, null, 2));
|
||||
|
||||
const triggerSteps = await Step.query().where({
|
||||
type: 'trigger',
|
||||
app_key: 'vonage',
|
||||
key: 'receiveMessage',
|
||||
});
|
||||
|
||||
if (triggerSteps.length === 0) return response.sendStatus(404);
|
||||
|
||||
for (const triggerStep of triggerSteps) {
|
||||
const flow = await triggerStep.$relatedQuery('flow');
|
||||
if (flow.status !== 'published') continue;
|
||||
|
||||
await handler(triggerStep.flowId, request, response);
|
||||
}
|
||||
|
||||
response.sendStatus(204);
|
||||
};
|
@@ -1,32 +1,45 @@
|
||||
import express, { Response, Router, NextFunction, RequestHandler } from 'express';
|
||||
import express, {
|
||||
Response,
|
||||
Router,
|
||||
NextFunction,
|
||||
RequestHandler,
|
||||
} from 'express';
|
||||
import multer from 'multer';
|
||||
|
||||
import { IRequest } from '@automatisch/types';
|
||||
import appConfig from '../config/app';
|
||||
import webhookHandlerByFlowId from '../controllers/webhooks/handler-by-flow-id';
|
||||
import webhookHandlerByConnectionIdAndRefValue from '../controllers/webhooks/handler-by-connection-id-and-ref-value';
|
||||
import webhookHandlerByApp from '../controllers/webhooks/handler-by-app';
|
||||
|
||||
const router = Router();
|
||||
const upload = multer();
|
||||
|
||||
router.use(upload.none());
|
||||
|
||||
router.use(express.text({
|
||||
limit: appConfig.requestBodySizeLimit,
|
||||
verify(req, res, buf) {
|
||||
(req as IRequest).rawBody = buf;
|
||||
},
|
||||
}));
|
||||
router.use(
|
||||
express.text({
|
||||
limit: appConfig.requestBodySizeLimit,
|
||||
verify(req, res, buf) {
|
||||
(req as IRequest).rawBody = buf;
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const exposeError = (handler: RequestHandler) => async (req: IRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
await handler(req, res, next);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
const exposeError =
|
||||
(handler: RequestHandler) =>
|
||||
async (req: IRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
await handler(req, res, next);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
};
|
||||
|
||||
function createRouteHandler(path: string, handler: (req: IRequest, res: Response, next: NextFunction) => void) {
|
||||
function createRouteHandler(
|
||||
path: string,
|
||||
handler: (req: IRequest, res: Response, next: NextFunction) => void
|
||||
) {
|
||||
const wrappedHandler = exposeError(handler);
|
||||
|
||||
router
|
||||
@@ -35,11 +48,18 @@ function createRouteHandler(path: string, handler: (req: IRequest, res: Response
|
||||
.put(wrappedHandler)
|
||||
.patch(wrappedHandler)
|
||||
.post(wrappedHandler);
|
||||
};
|
||||
}
|
||||
|
||||
createRouteHandler('/connections/:connectionId/:refValue', webhookHandlerByConnectionIdAndRefValue);
|
||||
createRouteHandler('/connections/:connectionId', webhookHandlerByConnectionIdAndRefValue);
|
||||
createRouteHandler(
|
||||
'/connections/:connectionId/:refValue',
|
||||
webhookHandlerByConnectionIdAndRefValue
|
||||
);
|
||||
createRouteHandler(
|
||||
'/connections/:connectionId',
|
||||
webhookHandlerByConnectionIdAndRefValue
|
||||
);
|
||||
createRouteHandler('/flows/:flowId', webhookHandlerByFlowId);
|
||||
createRouteHandler('/:flowId', webhookHandlerByFlowId);
|
||||
createRouteHandler('/apps/vonage', webhookHandlerByApp);
|
||||
|
||||
export default router;
|
||||
|
Reference in New Issue
Block a user