Microsoft Bot Builder中TurnContext在WebSocket监听内触发代理撤销错误
我想把WebSocket服务器返回的响应展示在Microsoft Bot Builder模拟器里,但在ws.addEventListener监听函数里遇到了问题。调试发现调用await turnContext.sendActivity(responseMessage)时,TurnContext丢失了作用域,没法向模拟器返回响应。控制台能收到WebSocket的响应,但触发了如下错误:
responseMessage :- BOT : To speak to an agent, press or say #
Insise Error
TypeError: Cannot perform 'get' on a proxy that has been revoked
at WebSocket. (D:\azure-bot\echo-bot\bots\stateManagementBot.js:155:59)
相关代码片段
ws.addEventListener('message',async function (event) { const msg = JSON.parse(event.data); if(msg.topic === 'aws/chat' && msg.contentType === 'application/json'){ const content = JSON.parse(msg.content); switch(content.Type){ case 'MESSAGE': try{ if(content.ParticipantRole !== 'CUSTOMER'){ responseMessage = `${content.DisplayName} : ${content.Content}`; console.log(`responseMessage :- ${responseMessage}`); await turnContext.sendActivity(responseMessage); } }catch(error){ console.log("Insise Error"); console.log(error); } break; case 'Event': if(content.ContentType.includes('ended')){ responseMessage = `Chat ended`; } } } });
完整代码片段
// The accessor names for the conversation data and user profile state property accessors. const CONVERSATION_DATA_PROPERTY = 'conversationData'; const USER_PROFILE_PROPERTY = 'userProfile'; let responseMessage; let connectionToken; let ws; class StateManagementBot extends ActivityHandler { constructor(conversationState, userState) { super(); // Create the state property accessors for the conversation data and user profile. this.conversationDataAccessor = conversationState.createProperty(CONVERSATION_DATA_PROPERTY); this.userProfileAccessor = userState.createProperty(USER_PROFILE_PROPERTY); // The state management objects for the conversation and user state. this.conversationState = conversationState; this.userState = userState; this.onMessage(async (turnContext, next) => { // Get the state properties from the turn context. // By calling next() you ensure that the next BotHandler is run.await next() if(turnContext.activity.text === '#'){ const url = await this.connectToAgent(turnContext.activity.text); const initialMessage = { topic: "aws/subscribe", content: { topics: ["aws/chat"] } }; console.log(url[1]); ws = new WebSocket(url[1]); console.log('Before Websocket....'); ws.addEventListener("open", () => { console.log('Websocket....'); ws.send(JSON.stringify(initialMessage)); }); console.log("Websocket Conn..."); const param = { ConnectionToken: connectionToken, /* required */ Content: turnContext.activity.text, /* required */ ContentType: 'text/plain', /* required */ }; connectparticipant.sendMessage(param, function(err, data) { console.log('Sending....'); if (err) console.log(err, err.stack); // an error occurred else console.log(data); // successful response }); ws.addEventListener('message',async function (event) { const msg = JSON.parse(event.data); if(msg.topic === 'aws/chat' && msg.contentType === 'application/json'){ const content = JSON.parse(msg.content); switch(content.Type){ case 'MESSAGE': try{ if(content.ParticipantRole !== 'CUSTOMER'){ responseMessage = `${content.DisplayName} : ${content.Content}`; console.log(`responseMessage :- ${responseMessage}`); await turnContext.sendActivity(responseMessage); } }catch(error){ console.log("Insise Error"); console.log(error); } break; case 'Event': if(content.ContentType.includes('ended')){ responseMessage = `Chat ended`; } } } }); } // By calling next() you ensure that the next BotHandler is run. await next(); });
解决方案
问题根源在于:TurnContext是当前消息处理周期的临时对象,周期结束后会被系统回收(代理被撤销)。而WebSocket的message事件是异步触发的,此时原TurnContext已经失效,无法再用来发送消息。需要通过保存对话引用,发起新的对话轮次来发送消息。
具体修改步骤:
在Bot类中添加必要属性
修改构造函数,传入adapter引用,并创建Map存储WebSocket与对话引用的映射,避免全局变量污染和多用户冲突:class StateManagementBot extends ActivityHandler { constructor(conversationState, userState, adapter) { super(); this.adapter = adapter; // 保存adapter实例 this.activeConversations = new Map(); // 存储活跃对话的引用 // 原构造函数中的其他代码 this.conversationDataAccessor = conversationState.createProperty(CONVERSATION_DATA_PROPERTY); this.userProfileAccessor = userState.createProperty(USER_PROFILE_PROPERTY); this.conversationState = conversationState; this.userState = userState; } }注意:创建Bot实例时要传入adapter参数。
保存对话引用
在onMessage处理函数中,当用户发送#时,提取并保存对话引用:this.onMessage(async (turnContext, next) => { if(turnContext.activity.text === '#'){ // ... 原有的WebSocket连接代码 // 保存对话引用 const conversationReference = TurnContext.getConversationReference(turnContext.activity); this.activeConversations.set(ws, conversationReference); // ... 其他代码 } await next(); });修改WebSocket消息回调
在回调中使用保存的对话引用,通过adapter.continueConversation发起新的对话轮次来发送消息:ws.addEventListener('message', async (event) => { const msg = JSON.parse(event.data); if (msg.topic === 'aws/chat' && msg.contentType === 'application/json') { const content = JSON.parse(msg.content); switch (content.Type) { case 'MESSAGE': try { if (content.ParticipantRole !== 'CUSTOMER') { const responseMessage = `${content.DisplayName} : ${content.Content}`; console.log(`responseMessage :- ${responseMessage}`); // 获取保存的对话引用 const conversationReference = this.activeConversations.get(ws); // 发起新的对话轮次发送消息 await this.adapter.continueConversation(conversationReference, async (newTurnContext) => { await newTurnContext.sendActivity(responseMessage); }); } } catch (error) { console.log("Inside Error"); console.log(error); } break; case 'Event': if (content.ContentType.includes('ended')) { const responseMessage = `Chat ended`; const conversationReference = this.activeConversations.get(ws); await this.adapter.continueConversation(conversationReference, async (newTurnContext) => { await newTurnContext.sendActivity(responseMessage); }); // 清理资源 this.activeConversations.delete(ws); ws.close(); } break; } } });清理全局变量
将原代码中的全局变量ws、responseMessage、connectionToken改为类的属性,避免多用户场景下的冲突:class StateManagementBot extends ActivityHandler { constructor(conversationState, userState, adapter) { super(); // ... 其他属性 this.connectionToken = null; } }
内容的提问来源于stack exchange,提问作者Mohammed Tariq

