UNPKG

@wearekadence/botbuilder-adapter-twilio-sms

Version:
284 lines 11.2 kB
"use strict"; /** * @module botbuilder-adapter-twilio-sms */ /** * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TwilioAdapter = void 0; const botbuilder_1 = require("botbuilder"); const debug_1 = __importDefault(require("debug")); const twilio_1 = __importDefault(require("twilio")); const botworker_1 = require("./botworker"); const debug = (0, debug_1.default)('botkit:twilio'); /** * Connect [Botkit](https://www.npmjs.com/package/botkit) or [BotBuilder](https://www.npmjs.com/package/botbuilder) to Twilio's SMS service. */ class TwilioAdapter extends botbuilder_1.BotAdapter { /** * Name used by Botkit plugin loader * @ignore */ name = 'Twilio SMS Adapter'; /** * Object containing one or more Botkit middlewares to bind automatically. * @ignore */ middlewares; /** * A specialized BotWorker for Botkit that exposes Twilio specific extension methods. * @ignore */ botkit_worker = botworker_1.TwilioBotWorker; options; api; // Twilio api /** * Create an adapter to handle incoming messages from Twilio's SMS service and translate them into a standard format for processing by your bot. * * Use with Botkit: *```javascript * const adapter = new TwilioAdapter({ * twilio_number: process.env.TWILIO_NUMBER, * account_sid: process.env.TWILIO_ACCOUNT_SID, * auth_token: process.env.TWILIO_AUTH_TOKEN, * validation_url: process.env.TWILIO_VALIDATION_URL * }); * const controller = new Botkit({ * adapter: adapter, * // ... other configuration options * }); * ``` * * Use with BotBuilder: *```javascript * const adapter = new TwilioAdapter({ * twilio_number: process.env.TWILIO_NUMBER, * account_sid: process.env.TWILIO_ACCOUNT_SID, * auth_token: process.env.TWILIO_AUTH_TOKEN, * validation_url: process.env.TWILIO_VALIDATION_URL * }); * // set up restify... * const server = restify.createServer(); * server.use(restify.plugins.bodyParser()); * server.post('/api/messages', (req, res) => { * adapter.processActivity(req, res, async(context) => { * // do your bot logic here! * }); * }); * ``` * * @param options An object containing API credentials, a webhook verification token and other options */ constructor(options) { super(); this.options = options; if (!options.twilio_number) { const err = 'twilio_number is a required part of the configuration.'; if (!this.options.enable_incomplete) { throw new Error(err); } else { console.error(err); } } if (!options.account_sid) { const err = 'account_sid is a required part of the configuration.'; if (!this.options.enable_incomplete) { throw new Error(err); } else { console.error(err); } } if (!options.auth_token) { const err = 'auth_token is a required part of the configuration.'; if (!this.options.enable_incomplete) { throw new Error(err); } else { console.error(err); } } if (this.options.enable_incomplete) { const warning = [ '', '****************************************************************************************', '* WARNING: Your adapter may be running with an incomplete/unsafe configuration. *', '* - Ensure all required configuration options are present *', '* - Disable the "enable_incomplete" option! *', '****************************************************************************************', '' ]; console.warn(warning.join('\n')); } try { this.api = (0, twilio_1.default)(this.options.account_sid, this.options.auth_token); } catch (err) { if (err) { if (!this.options.enable_incomplete) { throw new Error(err); } else { console.error(err); } } } this.middlewares = { spawn: [ async (bot, next) => { bot.api = this.api; next(); } ] }; } /** * Formats a BotBuilder activity into an outgoing Twilio SMS message. * @param activity A BotBuilder Activity object * @returns a Twilio message object with {body, from, to, mediaUrl} */ activityToTwilio(activity) { const message = { body: activity.text, from: this.options.twilio_number, to: activity.conversation.id, mediaUrl: undefined }; if (activity.channelData && activity.channelData.mediaUrl) { message.mediaUrl = activity.channelData.mediaUrl; } return message; } /** * Standard BotBuilder adapter method to send a message from the bot to the messaging API. * [BotBuilder reference docs](https://docs.microsoft.com/en-us/javascript/api/botbuilder-core/botadapter?view=botbuilder-ts-latest#sendactivities). * @param context A TurnContext representing the current incoming message and environment. (Not used) * @param activities An array of outgoing activities to be sent back to the messaging API. */ async sendActivities(context, activities) { const responses = []; for (let a = 0; a < activities.length; a++) { const activity = activities[a]; if (activity.type === botbuilder_1.ActivityTypes.Message) { const message = this.activityToTwilio(activity); const res = await this.api.messages.create(message); responses.push({ id: res.sid }); } else { debug('Unknown message type encountered in sendActivities: ', activity.type); } } return responses; } /** * Twilio SMS adapter does not support updateActivity. * @ignore */ // eslint-disable-next-line async updateActivity(context, activity) { debug('Twilio SMS does not support updating activities.'); } /** * Twilio SMS adapter does not support deleteActivity. * @ignore */ // eslint-disable-next-line async deleteActivity(context, reference) { debug('Twilio SMS does not support deleting activities.'); } /** * Standard BotBuilder adapter method for continuing an existing conversation based on a conversation reference. * [BotBuilder reference docs](https://docs.microsoft.com/en-us/javascript/api/botbuilder-core/botadapter?view=botbuilder-ts-latest#continueconversation) * @param reference A conversation reference to be applied to future messages. * @param logic A bot logic function that will perform continuing action in the form `async(context) => { ... }` */ async continueConversation(reference, logic) { const request = botbuilder_1.TurnContext.applyConversationReference({ type: 'event', name: 'continueConversation' }, reference, true); const context = new botbuilder_1.TurnContext(this, request); return this.runMiddleware(context, logic); } /** * Accept an incoming webhook request and convert it into a TurnContext which can be processed by the bot's logic. * @param req A request object from Restify or Express * @param res A response object from Restify or Express * @param logic A bot logic function in the form `async(context) => { ... }` */ async processActivity(req, res, logic) { if (await this.verifySignature(req, res) === true) { const event = req.body; const activity = { id: event.MessageSid, timestamp: new Date(), channelId: 'twilio-sms', conversation: { id: event.From }, from: { id: event.From }, recipient: { id: event.To }, text: event.Body, channelData: event, type: botbuilder_1.ActivityTypes.Message }; // Detect attachments if (event.NumMedia && parseInt(event.NumMedia) > 0) { // specify a different event type for Botkit activity.channelData.botkitEventType = 'picture_message'; } // create a conversation reference const context = new botbuilder_1.TurnContext(this, activity); context.turnState.set('httpStatus', 200); await this.runMiddleware(context, logic); // send http response back res.status(context.turnState.get('httpStatus')); if (context.turnState.get('httpBody')) { res.send(context.turnState.get('httpBody')); } else { res.end(); } } } /** * Validate that requests are coming from Twilio * @returns If signature is valid, returns true. Otherwise, sends a 400 error status via http response and then returns false. */ async verifySignature(req, res) { let twilioSignature; let validation_url; // Restify style if (!req.headers) { twilioSignature = req.header('x-twilio-signature'); validation_url = this.options.validation_url || (req.headers['x-forwarded-proto'] || (req.isSecure()) ? 'https' : 'http') + '://' + req.headers.host + req.url; } else { // express style twilioSignature = req.headers['x-twilio-signature']; validation_url = this.options.validation_url || ((req.headers['x-forwarded-proto'] || req.protocol) + '://' + req.hostname + req.originalUrl); } if (twilioSignature && twilio_1.default.validateRequest(this.options.auth_token, twilioSignature, validation_url, req.body)) { return true; } else { debug('Signature verification failed, Ignoring message'); res.status(400); res.send({ error: 'Invalid signature.' }); return false; } } } exports.TwilioAdapter = TwilioAdapter; //# sourceMappingURL=twilio_adapter.js.map