fix(gbdialog): Trying to save context.

This commit is contained in:
Rodrigo Rodriguez (pragmatismo.io) 2018-12-01 14:38:08 -02:00
parent f0a0cd36be
commit ce04290fcd
10 changed files with 128 additions and 174 deletions

2
.gitignore vendored
View file

@ -13,3 +13,5 @@
/packages/default.gbui/node_modules /packages/default.gbui/node_modules
/tmp /tmp
/work /work
/packages/default.gbdialog/bot.js
/packages/default.gbdialog/bot.ts

18
package-lock.json generated
View file

@ -3299,9 +3299,9 @@
} }
}, },
"@types/node": { "@types/node": {
"version": "9.6.36", "version": "9.6.40",
"resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.36.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.40.tgz",
"integrity": "sha512-Fbw+AdRLL01vv7Rk7bYaNPecqmKoinJHGbpKnDpbUZmUj/0vj3nLqPQ4CNBzr3q2zso6Cq/4jHoCAdH78fvJrw==" "integrity": "sha512-M3HHoXXndsho/sTbQML2BJr7/uwNhMg8P0D4lb+UsM65JQZx268faiz9hKpY4FpocWqpwlLwa8vevw8hLtKjOw=="
}, },
"async": { "async": {
"version": "1.5.2", "version": "1.5.2",
@ -3548,9 +3548,9 @@
} }
}, },
"botlib": { "botlib": {
"version": "0.1.6", "version": "0.1.7",
"resolved": "https://registry.npmjs.org/botlib/-/botlib-0.1.6.tgz", "resolved": "https://registry.npmjs.org/botlib/-/botlib-0.1.7.tgz",
"integrity": "sha512-NG/F7Yxhx/duehDzjI78mYMonZ03d+Gx+WtRmqj7TimGcc4xK1y4m7s+n9jt0XR+GYhj0jcA5uKA2LqDRtq22A==", "integrity": "sha512-vp8htUT/AL+pYXdiy9s13HFLbygCUorELw1dg1FEqHsfXQOoTlUvr52rNEeKikHvNYaXEEHqhv2F4pLRvEHIYw==",
"requires": { "requires": {
"async": "2.6.1", "async": "2.6.1",
"botbuilder": "4.1.3", "botbuilder": "4.1.3",
@ -3607,9 +3607,9 @@
} }
}, },
"@types/node": { "@types/node": {
"version": "9.6.36", "version": "9.6.40",
"resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.36.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.40.tgz",
"integrity": "sha512-Fbw+AdRLL01vv7Rk7bYaNPecqmKoinJHGbpKnDpbUZmUj/0vj3nLqPQ4CNBzr3q2zso6Cq/4jHoCAdH78fvJrw==" "integrity": "sha512-M3HHoXXndsho/sTbQML2BJr7/uwNhMg8P0D4lb+UsM65JQZx268faiz9hKpY4FpocWqpwlLwa8vevw8hLtKjOw=="
}, },
"botbuilder": { "botbuilder": {
"version": "4.1.3", "version": "4.1.3",

View file

@ -66,7 +66,7 @@
"botbuilder-choices": "^4.0.0-preview1.2", "botbuilder-choices": "^4.0.0-preview1.2",
"botbuilder-dialogs": "^4.1.5", "botbuilder-dialogs": "^4.1.5",
"botbuilder-prompts": "^4.0.0-preview1.2", "botbuilder-prompts": "^4.0.0-preview1.2",
"botlib": "0.1.6", "botlib": "^0.1.7",
"chai": "4.2.0", "chai": "4.2.0",
"child_process": "^1.0.2", "child_process": "^1.0.2",
"chokidar": "2.0.4", "chokidar": "2.0.4",

View file

@ -33,7 +33,9 @@
'use strict'; 'use strict';
import { TurnContext } from 'botbuilder'; import { TurnContext } from 'botbuilder';
import { WaterfallStepContext } from 'botbuilder-dialogs';
import { GBMinInstance } from 'botlib'; import { GBMinInstance } from 'botlib';
const WaitUntil = require('wait-until');
/** /**
* @fileoverview General Bots server core. * @fileoverview General Bots server core.
@ -42,20 +44,22 @@ import { GBMinInstance } from 'botlib';
export class DialogClass { export class DialogClass {
public min: GBMinInstance; public min: GBMinInstance;
public context: TurnContext; public context: TurnContext;
public step: WaterfallStepContext;
constructor(min: GBMinInstance) { constructor(min: GBMinInstance) {
this.min = min; this.min = min;
} }
public hear(text: string) { public async hear(cb) {
// TODO: await this.context.beginDialog('textPrompt', text); const id = Math.floor(Math.random() * 1000000000000);
this.min.cbMap[id] = cb;
await this.step.beginDialog('/feedback', { id: id });
} }
public talk(text: string) { public async talk(text: string) {
this.context.sendActivity(text); return await this.context.sendActivity(text);
} }
/** /**
* Generic function to call any REST API. * Generic function to call any REST API.
*/ */
@ -67,7 +71,5 @@ export class DialogClass {
/** /**
* Generic function to call any REST API. * Generic function to call any REST API.
*/ */
public post(url: string, data) { public post(url: string, data) {}
}
} }

View file

@ -184,7 +184,6 @@ export class GBCoreService implements IGBCoreService {
alter: alter, alter: alter,
force: force force: force
}); });
} else { } else {
const msg = `Database synchronization is disabled.`; const msg = `Database synchronization is disabled.`;
logger.info(msg); logger.info(msg);
@ -239,9 +238,15 @@ export class GBCoreService implements IGBCoreService {
} }
public async ensureProxy(port): Promise<string> { public async ensureProxy(port): Promise<string> {
try {
const ngrok = require('ngrok'); const ngrok = require('ngrok');
return await ngrok.connect({ port: port }); return await ngrok.connect({ port: port });
} catch (error) {
// There are false positive from ngrok regarding to no memory, but it's just
// lack of connection.
logger.verbose(error);
throw new Error('Error connecting to remote ngrok server, please check network connection.');
}
} }
public async saveInstance(fullInstance: any) { public async saveInstance(fullInstance: any) {
@ -313,7 +318,6 @@ export class GBCoreService implements IGBCoreService {
} }
public loadSysPackages(core: GBCoreService) { public loadSysPackages(core: GBCoreService) {
// NOTE: if there is any code before this line a semicolon // NOTE: if there is any code before this line a semicolon
// will be necessary before this line. // will be necessary before this line.
// Loads all system packages. // Loads all system packages.

View file

@ -43,21 +43,9 @@ const logger = require('../../../src/logger');
const request = require('request-promise-native'); const request = require('request-promise-native');
const AuthenticationContext = require('adal-node').AuthenticationContext; const AuthenticationContext = require('adal-node').AuthenticationContext;
import { import { AutoSaveStateMiddleware, BotFrameworkAdapter, ConversationState, MemoryStorage, UserState } from 'botbuilder';
AutoSaveStateMiddleware,
BotFrameworkAdapter,
ConversationState,
MemoryStorage,
UserState
} from 'botbuilder';
import { import { GBMinInstance, IGBAdminService, IGBConversationalService, IGBCoreService, IGBPackage } from 'botlib';
GBMinInstance,
IGBAdminService,
IGBConversationalService,
IGBCoreService,
IGBPackage
} from 'botlib';
import { GBAnalyticsPackage } from '../../analytics.gblib'; import { GBAnalyticsPackage } from '../../analytics.gblib';
import { GBCorePackage } from '../../core.gbapp'; import { GBCorePackage } from '../../core.gbapp';
import { GBCustomerSatisfactionPackage } from '../../customer-satisfaction.gbapp'; import { GBCustomerSatisfactionPackage } from '../../customer-satisfaction.gbapp';
@ -117,10 +105,7 @@ export class GBMinService {
// Serves default UI on root address '/'. // Serves default UI on root address '/'.
const uiPackage = 'default.gbui'; const uiPackage = 'default.gbui';
server.use( server.use('/', express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, 'build')));
'/',
express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, 'build'))
);
Promise.all( Promise.all(
instances.map(async instance => { instances.map(async instance => {
@ -136,7 +121,7 @@ export class GBMinService {
// Returns the instance object to clients requesting bot info. // Returns the instance object to clients requesting bot info.
let botId = req.params.botId; let botId = req.params.botId;
if (botId === '[default]'){ if (botId === '[default]') {
botId = bootInstance.botId; botId = bootInstance.botId;
} }
@ -171,15 +156,12 @@ export class GBMinService {
// Build bot adapter. // Build bot adapter.
const { min, adapter, conversationState } = await this.buildBotAdapter( const { min, adapter, conversationState } = await this.buildBotAdapter(instance);
instance
);
// Install default VBA module. // Install default VBA module.
deployer.deployPackageFromLocalPath(min, 'packages/default.gbdialog'); deployer.deployPackageFromLocalPath(min, 'packages/default.gbdialog');
// Call the loadBot context.activity for all packages. // Call the loadBot context.activity for all packages.
this.invokeLoadBot(appPackages, min, server); this.invokeLoadBot(appPackages, min, server);
@ -188,32 +170,17 @@ export class GBMinService {
const url = `/api/messages/${instance.botId}`; const url = `/api/messages/${instance.botId}`;
server.post(url, async (req, res) => { server.post(url, async (req, res) => {
return this.receiver( return await this.receiver(adapter, req, res, conversationState, min, instance, appPackages);
adapter,
req,
res,
conversationState,
min,
instance,
appPackages
);
}); });
logger.info( logger.info(`GeneralBots(${instance.engineName}) listening on: ${url}.`);
`GeneralBots(${instance.engineName}) listening on: ${url}.`
);
// Serves individual URL for each bot user interface. // Serves individual URL for each bot user interface.
const uiUrl = `/${instance.botId}`; const uiUrl = `/${instance.botId}`;
server.use( server.use(uiUrl, express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, 'build')));
uiUrl,
express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, 'build'))
);
logger.info(`Bot UI ${uiPackage} accessible at: ${uiUrl}.`); logger.info(`Bot UI ${uiPackage} accessible at: ${uiUrl}.`);
const state = `${instance.instanceId}${Math.floor( const state = `${instance.instanceId}${Math.floor(Math.random() * 1000000000)}`;
Math.random() * 1000000000
)}`;
// Clients get redirected here in order to create an OAuth authorize url and redirect them to AAD. // Clients get redirected here in order to create an OAuth authorize url and redirect them to AAD.
// There they will authenticate and give their consent to allow this app access to // There they will authenticate and give their consent to allow this app access to
@ -226,9 +193,7 @@ export class GBMinService {
); );
authorizationUrl = `${authorizationUrl}?response_type=code&client_id=${ authorizationUrl = `${authorizationUrl}?response_type=code&client_id=${
min.instance.authenticatorClientId min.instance.authenticatorClientId
}&redirect_uri=${min.instance.botEndpoint}/${ }&redirect_uri=${min.instance.botEndpoint}/${min.instance.botId}/token`;
min.instance.botId
}/token`;
res.redirect(authorizationUrl); res.redirect(authorizationUrl);
}); });
@ -238,23 +203,16 @@ export class GBMinService {
// access token that can be used to access the user owned resource. // access token that can be used to access the user owned resource.
server.get(`/${min.instance.botId}/token`, async (req, res) => { server.get(`/${min.instance.botId}/token`, async (req, res) => {
const state = await min.adminService.getValue( const state = await min.adminService.getValue(min.instance.instanceId, 'AntiCSRFAttackState');
min.instance.instanceId,
'AntiCSRFAttackState'
);
if (req.query.state !== state) { if (req.query.state !== state) {
const msg = const msg = 'WARNING: state field was not provided as anti-CSRF token';
'WARNING: state field was not provided as anti-CSRF token';
logger.error(msg); logger.error(msg);
throw new Error(msg); throw new Error(msg);
} }
const authenticationContext = new AuthenticationContext( const authenticationContext = new AuthenticationContext(
UrlJoin( UrlJoin(min.instance.authenticatorAuthorityHostUrl, min.instance.authenticatorTenant)
min.instance.authenticatorAuthorityHostUrl,
min.instance.authenticatorTenant
)
); );
const resource = 'https://graph.microsoft.com'; const resource = 'https://graph.microsoft.com';
@ -271,47 +229,16 @@ export class GBMinService {
logger.error(msg); logger.error(msg);
res.send(msg); res.send(msg);
} else { } else {
await this.adminService.setValue( await this.adminService.setValue(instance.instanceId, 'refreshToken', token.refreshToken);
instance.instanceId, await this.adminService.setValue(instance.instanceId, 'accessToken', token.accessToken);
'refreshToken', await this.adminService.setValue(instance.instanceId, 'expiresOn', token.expiresOn.toString());
token.refreshToken await this.adminService.setValue(instance.instanceId, 'AntiCSRFAttackState', null);
);
await this.adminService.setValue(
instance.instanceId,
'accessToken',
token.accessToken
);
await this.adminService.setValue(
instance.instanceId,
'expiresOn',
token.expiresOn.toString()
);
await this.adminService.setValue(
instance.instanceId,
'AntiCSRFAttackState',
null
);
res.redirect(min.instance.botEndpoint); res.redirect(min.instance.botEndpoint);
} }
} }
); );
}); });
// Setups handlers.
// send: function (context.activity, next) {
// logger.info(
// `[SND]: ChannelID: ${context.activity.address.channelId}, ConversationID: ${context.activity.address.conversation},
// Type: ${context.activity.type} `)
// this.core.createMessage(
// this.min.conversation,
// this.min.conversation.startedBy,
// context.activity.source,
// (data, err) => {
// logger.info(context.activity.source)
// }
// )
// next()
}) })
); );
} }
@ -388,8 +315,10 @@ export class GBMinService {
min.conversationalService = this.conversationalService; min.conversationalService = this.conversationalService;
min.adminService = this.adminService; min.adminService = this.adminService;
min.instance = await this.core.loadInstance(min.botId); min.instance = await this.core.loadInstance(min.botId);
min.userProfile = conversationState.createProperty('userProfile'); min.userProfile = conversationState.createProperty('userProfile');
const dialogState = conversationState.createProperty('dialogState'); const dialogState = conversationState.createProperty('dialogState');
min.dialogs = new DialogSet(dialogState); min.dialogs = new DialogSet(dialogState);
min.dialogs.add(new TextPrompt('textPrompt')); min.dialogs.add(new TextPrompt('textPrompt'));
@ -428,7 +357,7 @@ export class GBMinService {
/** /**
* Bot Service hook method. * Bot Service hook method.
*/ */
private receiver( private async receiver(
adapter: BotFrameworkAdapter, adapter: BotFrameworkAdapter,
req: any, req: any,
res: any, res: any,
@ -437,8 +366,9 @@ export class GBMinService {
instance: any, instance: any,
appPackages: any[] appPackages: any[]
) { ) {
return adapter.processActivity(req, res, async context => { return await adapter.processActivity(req, res, async context => {
const state = conversationState.get(context); // Get loaded user state
const state = await conversationState.get(context);
const step = await min.dialogs.createContext(context, state); const step = await min.dialogs.createContext(context, state);
step.context.activity.locale = 'en-US'; // TODO: Make dynamic. step.context.activity.locale = 'en-US'; // TODO: Make dynamic.
@ -454,18 +384,16 @@ export class GBMinService {
}); });
user.loaded = true; user.loaded = true;
user.subjects = []; user.subjects = [];
user.cb = null;
await min.userProfile.set(step.context, user); await min.userProfile.set(step.context, user);
} }
logger.info( logger.info(
`User>: ${context.activity.text} (${context.activity.type}, ${ `User>: ${context.activity.text} (${context.activity.type}, ${context.activity.name}, ${
context.activity.name context.activity.channelId
}, ${context.activity.channelId}, {context.activity.value})` }, {context.activity.value})`
); );
if ( if (context.activity.type === 'conversationUpdate' && context.activity.membersAdded.length > 0) {
context.activity.type === 'conversationUpdate' &&
context.activity.membersAdded.length > 0
) {
const member = context.activity.membersAdded[0]; const member = context.activity.membersAdded[0];
if (member.name === 'GeneralBots') { if (member.name === 'GeneralBots') {
logger.info(`Bot added to conversation, starting chat...`); logger.info(`Bot added to conversation, starting chat...`);
@ -480,12 +408,15 @@ export class GBMinService {
} }
// Processes messages. // Processes messages.
} else if (context.activity.type === 'message') { } else if (context.activity.type === 'message') {
// Checks for /admin request. // Checks for /admin request.
if (context.activity.text === 'vba') { if (context.activity.text === 'vba') {
min.sandbox.context = context; min.sandbox.context = context;
min.sandbox.step = step;
min.sandbox['bot'].bind(min.sandbox); min.sandbox['bot'].bind(min.sandbox);
min.sandbox['bot']();
await min.sandbox['bot']();
} else if (context.activity.text === 'admin') { } else if (context.activity.text === 'admin') {
await step.beginDialog('/admin'); await step.beginDialog('/admin');
@ -497,7 +428,9 @@ export class GBMinService {
// Otherwise, continue to the active dialog in the stack. // Otherwise, continue to the active dialog in the stack.
} else { } else {
if (step.activeDialog) { const user = await min.userProfile.get(context, {});
if (step.activeDialog || user.dialog) {
await step.continueDialog(); await step.continueDialog();
} else { } else {
await step.beginDialog('/answer', { await step.beginDialog('/answer', {
@ -508,7 +441,6 @@ export class GBMinService {
// Processes events. // Processes events.
} else if (context.activity.type === 'event') { } else if (context.activity.type === 'event') {
// Empties dialog stack before going to the target. // Empties dialog stack before going to the target.
await step.endAll(); await step.endAll();
@ -539,13 +471,12 @@ export class GBMinService {
await step.continueDialog(); await step.continueDialog();
} }
} }
await conversationState.saveChanges(context, true);
} catch (error) { } catch (error) {
const msg = `ERROR: ${error.message} ${error.stack ? error.stack : ''}`; const msg = `ERROR: ${error.message} ${error.stack ? error.stack : ''}`;
logger.error(msg); logger.error(msg);
await step.context.sendActivity( await step.context.sendActivity(Messages[step.context.activity.locale].very_sorry_about_error);
Messages[step.context.activity.locale].very_sorry_about_error
);
await step.beginDialog('/ask', { isReturning: true }); await step.beginDialog('/ask', { isReturning: true });
} }
}); });

View file

@ -76,13 +76,15 @@ export class GBVMService implements IGBCoreService {
// Convert TS into JS. // Convert TS into JS.
const tsfile = `bot.ts`; const tsfile = `bot.ts`;
const tsc = new TSCompiler(); const tsc = new TSCompiler();
tsc.compile([UrlJoin(path, tsfile)]); // TODO: tsc.compile([UrlJoin(path, tsfile)]);
// Run JS into the GB context. // Run JS into the GB context.
const jsfile = `bot.js`; const jsfile = `bot.js`;
localPath = UrlJoin(path, jsfile); localPath = UrlJoin(path, jsfile);
if (fs.existsSync(localPath)) { if (fs.existsSync(localPath)) {
let code: string = fs.readFileSync(localPath, 'utf8'); let code: string = fs.readFileSync(localPath, 'utf8');
code = code.replace(/^.*exports.*$/gm, ''); code = code.replace(/^.*exports.*$/gm, '');
code = code.replace(/this\./gm, 'await this.');
code = code.replace(/function/gm, 'async function');
const sandbox: DialogClass = new DialogClass(min); const sandbox: DialogClass = new DialogClass(min);
const context = vm.createContext(sandbox); const context = vm.createContext(sandbox);
vm.runInContext(code, context); vm.runInContext(code, context);

View file

@ -78,35 +78,48 @@ export class FeedbackDialog extends IGBDialog {
]) ])
); );
min.dialogs.add(new WaterfallDialog('/feedback', [ min.dialogs.add(
new WaterfallDialog('/feedback', [
async step => { async step => {
const locale = step.context.activity.locale; const locale = step.context.activity.locale;
if (step.result.fromMenu) {
await step.context.sendActivity(Messages[locale].about_suggestions);
}
await step.prompt('textPrompt', Messages[locale].what_about_service); await step.context.sendActivity(Messages[locale].about_suggestions);
return await step.next(); step.activeDialog.state.cbId = step.options['id'];
return await step.prompt('textPrompt', Messages[locale].what_about_service);
}, },
async step => { async step => {
const locale = step.context.activity.locale;
const rate = await AzureText.getSentiment(
min.instance.textAnalyticsKey,
min.instance.textAnalyticsEndpoint,
min.conversationalService.getCurrentLanguage(step),
step.result
);
if (rate > 0.5) { console.log(step.result);
await step.context.sendActivity(Messages[locale].glad_you_liked);
} else { // min.sandbox.context = step.context;
await step.context.sendActivity(Messages[locale].we_will_improve); // min.sandbox.step = step;
let cbId = step.activeDialog.state.cbId;
let cb = min.cbMap[cbId];
cb.bind({ step: step, context: step.context });
await cb();
// const locale = step.context.activity.locale;
// const rate = await AzureText.getSentiment(
// min.instance.textAnalyticsKey,
// min.instance.textAnalyticsEndpoint,
// min.conversationalService.getCurrentLanguage(step),
// step.result
// );
// if (rate > 0.5) {
// await step.context.sendActivity(Messages[locale].glad_you_liked);
// } else {
// await step.context.sendActivity(Messages[locale].we_will_improve);
// // TODO: Record.
// }
// await step.replaceDialog('/ask', { isReturning: true });
// TODO: Record.
}
await step.replaceDialog('/ask', { isReturning: true });
return await step.next(); return await step.next();
} }
])); ])
);
} }
} }

View file

@ -35,5 +35,4 @@ this.talk ("Please, what's your e-mail address?")
let email = this.hear() let email = this.hear()
this.talk("Thanks, sending e-mail to: " + email); this.talk("Thanks, sending e-mail to: " + email);
this.sendEmail(email, "Message from VBA Bot", "Yes, I can send e-mails."); this.sendEmail(email, "Message from VBA Bot", "Yes, I can send e-mails.");
%> %>

View file

@ -93,6 +93,7 @@ export class GBServer {
// Ensures cloud / on-premises infrastructure is setup. // Ensures cloud / on-premises infrastructure is setup.
logger.info(`Establishing a development local proxy (ngrok)...`); logger.info(`Establishing a development local proxy (ngrok)...`);
const proxyAddress: string = await core.ensureProxy(port); const proxyAddress: string = await core.ensureProxy(port);
logger.info(`Deploying packages...`); logger.info(`Deploying packages...`);