We've created a Custom Botbuilder Adapter to connect to the Vonage API called botbuilder-adapter-vonage-js. To test the Adapter's basic functionality, with a basic bot reply, we send an SMS to the Vonage number and should get an SMS reply back "Hello Back", but instead receive the error below:
[onTurnError] unhandled error: TypeError: bot.reply is not a function
Not sure how to actually debug the Custom Adapter to find where it is broken.
Would be great to find someone familiar with either the Botkit Core library and Botkit Platform Adapters could help with this. I've attached the Express Server (webhook-server.js) below.
// webhook-server.js
require('dotenv').config();
const express = require('express');
const app = express();
const port = 3000;
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
const SendMessagesAPI = require('./Vonage-SEND-messages-api');
const VonageAdapter = require('botbuilder-adapter-vonage-js');
const Botkit = require('botkit');
const {
BotFrameworkAdapter,
InspectionMiddleware,
MemoryStorage,
InspectionState,
UserState,
ConversationState,
} = require('botbuilder');
const { MicrosoftAppCredentials } = require('botframework-connector');
// This bot's main dialog.
const { IntersectionBot } = require('./bot');
const { Message } = require('@vonage/server-sdk');
const creds = {
apiKey: process.env.VONAGE_API_KEY,
apiSecret: process.env.VONAGE_API_SECRET,
applicationId: process.env.VONAGE_APPLICATION_ID,
privateKey: process.env.VONAGE_APPLICATION_PRIVATE_KEY_PATH,
};
const config = {
to_number: process.env.TO_NUMBER,
from_number: process.env.FROM_NUMBER,
// enable_incomplete: true
};
// Create Adapter
const adapter = new VonageAdapter(creds, config);
// Create the Storage provider and the various types of BotState.
const memoryStorage = new MemoryStorage();
const inspectionState = new InspectionState(memoryStorage);
const userState = new UserState(memoryStorage);
const conversationState = new ConversationState(memoryStorage);
// Create and add the InspectionMiddleware to the adapter.
adapter.use(
new InspectionMiddleware(
inspectionState,
userState,
conversationState,
new MicrosoftAppCredentials(
process.env.MicrosoftAppId,
process.env.MicrosoftAppPassword
)
)
);
app.post('/webhooks/dlr', (req, res) => {
res.status(200).end();
});
// Catch-all for errors.
adapter.onTurnError = async (, error) => {
// This check writes out errors to console log .vs. app insights.
// NOTE: In production environment, you should consider logging this to Azure
// application insights. See https://aka.ms/bottelemetry for telemetry
// configuration instructions.
console.error(`\n [onTurnError] unhandled error: ${error}`);
// Send a trace activity, which will be displayed in Bot Framework Emulator
await .sendTraceActivity(
'OnTurnError Trace',
`${error}`,
'https://www.botframework.com/schemas/error',
'TurnError'
);
// Send a message to the user
await .sendActivity('The bot encountered an error or bug.');
await .sendActivity(
'To continue to run this bot, please fix the bot source code.'
);
// Clear out state
await conversationState.clear();
};
// Create the main dialog.
const bot = new IntersectionBot(conversationState, userState);
// Listen for incoming requests.
app.post('/webhooks/inbound', (req, res) => {
console.log('/webhooks/inbound req.body', req.body);
adapter.processActivity(req, res, async () => {
console.log(context);
// [onTurnError] unhandled error: TypeError: Cannot read property 'from' of undefined
// await bot.run();
// [onTurnError] unhandled error: TypeError: .reply is not a function
// await .reply('I heard a message!');
// [onTurnError] unhandled error: TypeError: bot.reply is not a function
await bot.reply('Hello Back!');
});
res.status(200).end();
});
app.post('/webhooks/status', (req, res) => {
res.status(200).end();
});
app.listen(port, () => {
console.log(` Server running at http://localhost:${port}`);
});
Response
Server running at http://localhost:3000
/webhooks/inbound req.body {
message_uuid: 'e93a3007-f7a5-436a-8ba7-c46d64343d80',
to: { type: 'sms', number: '12018994297' },
from: { type: 'sms', number: '15754947000' },
timestamp: '2021-08-27T21:14:51.228Z',
usage: { price: '0.0057', currency: 'EUR' },
message: {
content: { type: 'text', text: 'Hello' },
sms: { num_messages: '1' }
},
direction: 'inbound'
}
TurnContext {
_respondedRef: { responded: false },
_turnState: TurnContextStateCollection(2) [Map] {
'httpStatus' => 200,
Symbol(state) => { state: [Object], hash: '{}' }
},
_onSendActivities: [],
_onUpdateActivity: [],
_onDeleteActivity: [],
_turn: 'turn',
_locale: 'locale',
bufferedReplyActivities: [],
_adapter: VonageAdapter {
middleware: MiddlewareSet { middleware: [Array] },
BotIdentityKey: Symbol(BotIdentity),
OAuthScopeKey: Symbol(OAuthScope),
name: 'Vonage Adapter',
middlewares: null,
botkit_worker: [class VonageBotWorker extends BotWorker],
credentials: {
apiKey: '4f2ff535',
apiSecret: 'jtYzPbh3MXr8M1Hr',
applicationId: '978500cf-7ea8-4d7b-ac54-2b42f67b28a2',
privateKey: './private.key'
},
options: {},
to_number: '15754947000',
from_number: '12018994297',
enable_incomplete: undefined,
turnError: [AsyncFunction (anonymous)]
},
_activity: {
id: 'e93a3007-f7a5-436a-8ba7-c46d64343d80',
timestamp: 2021-08-27T21:14:39.573Z,
channelId: 'vonage-sms',
conversation: { id: '15754947000' },
from: { id: '15754947000' },
recipient: { id: '12018994297' },
text: 'Hello',
channelData: {
message_uuid: 'e93a3007-f7a5-436a-8ba7-c46d64343d80',
to: [Object],
from: [Object],
timestamp: '2021-08-27T21:14:51.228Z',
usage: [Object],
message: [Object],
direction: 'inbound'
},
type: 'message'
}
}
[onTurnError] unhandled error: TypeError: bot.reply is not a function
- Botkit version:
- Messaging Platform: Vonage
- Node version: v14.16.1
- Os: MAC
It looks like your custom adapter is built using both Botkit and BotFramework similar to other Botkit adapters. However, your bot's implementation aligns more with a bot built only for BotFramework yet you are trying to call the
reply()method that belongs to a Botkit bot.For example, in Botkit's 'botbuilder-adapter-twilio-sms' adapter, you are presented with two ways to utilize the adapter. In the first, under Botkit Basics, an adapter is created which is then consumed by Botkit to form the controller. This then allows you to access the
reply()method callable from a Botkit bot.In the second, under BotBuilder Basics, an adapter is created which is then utilized within the Express server's
/api/messagesendpoint. Inbound messages are passed to the adapter'sprocessActivity()method where the bot then responds using thesendActivity()method, callable from within a BotFramework adapter's context.Narrowing down which implementation you intend to use I believe will alleviate the error you are receiving.