For some reason, the BotFramework does not work well with callbacks which is why you are getting the "Cannot perform 'get' on a proxy that has been revoked" error. A solution - yet complicated - to this is to create a proactive message API endpoint, call a request to it from the timeout callback, and then execute the rest of the bot calls from the proactive message. I would recommend taking a look at the Proactive Messaging Sample before getting started with code below.
index.js file
We are going to add an /api/notify endpoint to our Restify Server that will be hit when the timeout finishes. I would recommend adding a method to your bot to handle sending proactive messages, so you can keep all of the state and dialog elements contained in your bot instead of elevating them to the index file. Note, you will have to pass the adapter as a parameter to your bot.
let bot = new Bot(adapter, conversationState, userState);
...
server.get('/api/notify/:conversationId', (req, res) => {
bot.sendProactiveMessages(req.params.conversationId);
res.send(204);
});
Dialog
In this step of the dialog, we are adding a responded attribute to the user profile - you can also add this to conversation state - and setting the default value to false. Then instead of configuring the callback to access the state and message the user, simply use an HTTP client like Axios or Request to make a get request with the conversation id as a URL parameter to the endpoint we just created in the step above.
When the user responds to the next prompt, update the responded value to true so we can tell if the user responded from the proactive message.
async captureName(step) {
const profile = await this.profileAccessor.get(step.context);
profile.name = step.result;
profile.responded = false;
this.profileAccessor.set(step.context, profile);
const { conversation: { id }} = TurnContext.getConversationReference(step.context.activity);
setTimeout(() => {
axios.get(`http://localhost:3978/api/notify/${id}`)
.then(() => {})
.catch(error => console.log(error));
}, 60000);
return await step.next();
}
async promptForCity(step) {
return await step.prompt(CITY_PROMPT, "What city are your from?");
}
async captureCity(step) {
const profile = await this.profileAccessor.get(step.context);
profile.city = step.result;
profile.responded = true;
this.profileAccessor.set(step.context, profile);
return await step.next();
}
Bot
In the proactive messaging sample, all of the conversation references are stored in an object. We can use the conversation id from the get request as the key value to retrieve the conversation reference and use the reference to continue the conversation. From the proactive message, you can send activities, access and update state, cancel dialogs, and all of the other normal functions you can do with a bot.
class Bot extends ActivityHandler{
constructor(adapter, conversationState, userState) {
super();
this.adapter = adapter;
this.conversationReferences = {};
this.conversationState = conversationState;
this.userState = userState;
// Configure properties
this.profileAccessor = this.userState.createProperty(USER_PROFILE);
this.dialogState = this.conversationState.createProperty(DIALOG_STATE);
}
async sendProactiveMessages(conversationId) {
const conversationReference = this.conversationReferences[conversationId];
conversationReference && await this.adapter.continueConversation(conversationReference, async context => {
const { responded } = await this.profileAccessor.get(context);
if (!responded) {
const dc = await this.dialogs.createContext(context);
await dc.cancelAllDialogs();
await context.sendActivity('Sorry you took too long to respond..');
await this.conversationState.saveChanges(context);
}
});
}
}
I know this a bit complicated for a simple action, but I hope this helps!