feat(vonage): Introduce Vonage API receive and send message integrations

This commit is contained in:
Faruk AYDIN
2023-09-18 22:41:29 +02:00
parent c193f9334f
commit a3d50e2766
12 changed files with 311 additions and 18 deletions

View File

@@ -0,0 +1,3 @@
import sendMessage from './send-message';
export default [sendMessage];

View File

@@ -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 });
},
});

View 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

View 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,
};

View File

@@ -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;

View 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;

View File

View 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,
});

View File

@@ -0,0 +1,3 @@
import receiveMessage from './receive-message';
export default [receiveMessage];

View File

@@ -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
},
});

View 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);
};

View File

@@ -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;