diff --git a/README.md b/README.md index a9ecf6c5..52dfdfe0 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,10 @@ | Releases | [![General Bots](https://img.shields.io/npm/dt/botserver.svg?logo=npm&label=botserver)](https://www.npmjs.com/package/botserver/) [![.gbapp lib](https://img.shields.io/npm/dt/botlib.svg?logo=npm&label=botlib)](https://www.npmjs.com/package/botlib/) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)| | [Docker Image](https://github.com/lpicanco/docker-botserver) | ![Docker Automated build](https://img.shields.io/docker/automated/lpicanco/botserver.svg) ![Docker Build Status](https://img.shields.io/docker/build/lpicanco/botserver.svg) ![MicroBadger Size](https://img.shields.io/microbadger/image-size/lpicanco/botserver.svg) ![MicroBadger Layers](https://img.shields.io/microbadger/layers/lpicanco/botserver.svg) ![Docker Pulls](https://img.shields.io/docker/pulls/lpicanco/botserver.svg)
*Provided by [@lpicanco](https://github.com/lpicanco/docker-botserver)* | + #### Watch a video about easeness authoring of bot packages, development environment and self-deployment -* Now with General Bots you can press F5 on Visual Studio to get a bot factory on your environment* published on November 10th, 2018. +* Now with the General Bots server you can press F5 on Visual Studio to get a bot factory on your environment* published on November 10th, 2018. [![General Bot Video](https://raw.githubusercontent.com/pragmatismo-io/BotServer/master/docs/images/video-01-thumb.jpg)](https://www.youtube.com/watch?v=AfKTwljoMOs) @@ -20,7 +21,7 @@ Welcome to General Bot Community Edition ![General Bot Logo](https://raw.githubusercontent.com/pragmatismo-io/BotServer/master/logo.png) -General Bot is a package based chat bot server focused in convention over configuration and code-less approaches, which brings software packages and application server concepts to help parallel bot development. +General Bot is a strongly typed package based chat bot server focused in convention over configuration and code-less approaches, which brings software packages and application server concepts to help parallel bot development. ## Sample Package #1: [default.gbdialog (VBA)](https://github.com/pragmatismo-io/BotServer/tree/master/packages/default.gbdialog) diff --git a/packages/admin.gbapp/dialogs/AdminDialog.ts b/packages/admin.gbapp/dialogs/AdminDialog.ts index 287b2502..4d3b4a03 100644 --- a/packages/admin.gbapp/dialogs/AdminDialog.ts +++ b/packages/admin.gbapp/dialogs/AdminDialog.ts @@ -38,7 +38,7 @@ const UrlJoin = require('url-join'); import { BotAdapter } from 'botbuilder'; -import { WaterfallDialog } from 'botbuilder-dialogs'; +import { WaterfallDialog, WaterfallStep, WaterfallStepContext } from 'botbuilder-dialogs'; import { GBMinInstance } from 'botlib'; import { IGBDialog } from 'botlib'; import { GBConfigService } from '../../core.gbapp/services/GBConfigService'; @@ -60,28 +60,35 @@ export class AdminDialog extends IGBDialog { await deployer.undeployPackageFromLocalPath(min.instance, UrlJoin('packages', packageName)); } + public static isSharePointPath(path: string) { + return path.indexOf('sharepoint.com') > 0; + } + public static async deployPackageCommand(min: GBMinInstance, text: string, deployer: GBDeployer) { const packageName = text.split(' ')[1]; - const additionalPath = GBConfigService.get('ADDITIONAL_DEPLOY_PATH'); - if (!additionalPath) - { - throw new Error('ADDITIONAL_DEPLOY_PATH is not set and deployPackage was called.'); + + if (AdminDialog.isSharePointPath(packageName)) { + await deployer.deployFromSharePoint(min.instance.instanceId, packageName); + } else { + const additionalPath = GBConfigService.get('ADDITIONAL_DEPLOY_PATH'); + if (!additionalPath) { + throw new Error('ADDITIONAL_DEPLOY_PATH is not set and deployPackage was called.'); + } + await deployer.deployPackageFromLocalPath(min, UrlJoin(additionalPath, packageName)); } - await deployer.deployPackageFromLocalPath(min, UrlJoin(additionalPath, packageName)); } public static async rebuildIndexPackageCommand(min: GBMinInstance, text: string, deployer: GBDeployer) { await deployer.rebuildIndex(min.instance); } - public static async addConnectionCommand(min: GBMinInstance, text: any) { + public static async addConnectionCommand(min: GBMinInstance, text: any) { const packageName = text.split(' ')[1]; const importer = new GBImporter(min.core); const admin = new GBAdminService(min.core); // TODO: await admin.addConnection } - /** * Setup dialogs flows and define services call. * @@ -94,6 +101,8 @@ export class AdminDialog extends IGBDialog { const importer = new GBImporter(min.core); const deployer = new GBDeployer(min.core, importer); + AdminDialog.setupSecurityDialogs(min); + min.dialogs.add( new WaterfallDialog('/admin', [ async step => { @@ -151,7 +160,7 @@ export class AdminDialog extends IGBDialog { return await step.replaceDialog('/admin', { firstRun: false }); } else if (cmdName === 'setupSecurity') { - await AdminDialog.setupSecurity(min, step); + return await step.beginDialog('/setupSecurity'); } else { unknownCommand = true; } @@ -159,7 +168,7 @@ export class AdminDialog extends IGBDialog { if (unknownCommand) { await step.context.sendActivity(Messages[locale].unknown_command); } else { - await step.context.sendActivity(Messages[locale].finshed_working(cmdName)); + await step.context.sendActivity(Messages[locale].finished_working); } await step.endDialog(); @@ -169,16 +178,66 @@ export class AdminDialog extends IGBDialog { ); } - private static async setupSecurity(min: any, step: any) { - const locale = step.activity.locale; - const state = `${min.instance.instanceId}${Math.floor(Math.random() * 1000000000)}`; - await min.adminService.setValue(min.instance.instanceId, 'AntiCSRFAttackState', state); - const url = `https://login.microsoftonline.com/${min.instance.authenticatorTenant}/oauth2/authorize?client_id=${ - min.instance.authenticatorClientId - }&response_type=code&redirect_uri=${min.instance.botEndpoint}/${ - min.instance.botId - }/token&state=${state}&response_mode=query`; + private static setupSecurityDialogs(min: any) { + min.dialogs.add( + new WaterfallDialog('/setupSecurity', [ + async step => { + const locale = step.context.activity.locale; + const prompt = Messages[locale].enter_authenticator_tenant; - await step.sendActivity(Messages[locale].consent(url)); + return await step.prompt('textPrompt', prompt); + }, + async step => { + step.activeDialog.state.authenticatorTenant = step.result; + const locale = step.context.activity.locale; + const prompt = Messages[locale].enter_authenticator_authority_host_url; + + return await step.prompt('textPrompt', prompt); + }, + async step => { + step.activeDialog.state.authenticatorAuthorityHostUrl = step.result; + const locale = step.context.activity.locale; + const prompt = Messages[locale].enter_authenticator_client_id; + + return await step.prompt('textPrompt', prompt); + }, + async step => { + step.activeDialog.state.authenticatorClientId = step.result; + const locale = step.context.activity.locale; + const prompt = Messages[locale].enter_authenticator_client_secret; + + return await step.prompt('textPrompt', prompt); + }, + async step => { + step.activeDialog.state.authenticatorClientSecret = step.result; + + await min.adminService.updateSecurityInfo( + min.instance.instanceId, + step.activeDialog.state.authenticatorTenant, + step.activeDialog.state.authenticatorAuthorityHostUrl, + step.activeDialog.state.authenticatorClientId, + step.activeDialog.state.authenticatorClientSecret + ); + + const locale = step.context.activity.locale; + const state = `${min.instance.instanceId}${Math.floor(Math.random() * 1000000000)}`; + + await min.adminService.setValue(min.instance.instanceId, 'AntiCSRFAttackState', state); + + const url = `https://login.microsoftonline.com/${min.instance.authenticatorTenant}/oauth2/authorize?client_id=${ + min.instance.authenticatorClientId + }&response_type=code&redirect_uri=${UrlJoin( + min.instance.botEndpoint, + min.instance.botId, + '/token' + )}&state=${state}&response_mode=query`; + + await step.context.sendActivity(Messages[locale].consent(url)); + + return await step.replaceDialog('/ask', {isReturning: true}); + } + ]) + ); } + } diff --git a/packages/admin.gbapp/models/AdminModel.ts b/packages/admin.gbapp/models/AdminModel.ts index e7d26acb..a747f704 100644 --- a/packages/admin.gbapp/models/AdminModel.ts +++ b/packages/admin.gbapp/models/AdminModel.ts @@ -41,7 +41,8 @@ import { CreatedAt, Model, Table, - UpdatedAt + UpdatedAt, + DataType } from 'sequelize-typescript'; @Table @@ -53,7 +54,7 @@ export class GuaribasAdmin extends Model { @Column public key: string; - @Column + @Column(DataType.STRING(1024)) public value: string; @Column diff --git a/packages/admin.gbapp/services/GBAdminService.ts b/packages/admin.gbapp/services/GBAdminService.ts index aee36868..80c2d1f9 100644 --- a/packages/admin.gbapp/services/GBAdminService.ts +++ b/packages/admin.gbapp/services/GBAdminService.ts @@ -38,19 +38,20 @@ import { AuthenticationContext, TokenResponse } from 'adal-node'; import { IGBCoreService } from 'botlib'; +import { GuaribasInstance } from '../../core.gbapp/models/GBModel'; import { GuaribasAdmin } from '../models/AdminModel'; const UrlJoin = require('url-join'); const msRestAzure = require('ms-rest-azure'); const PasswordGenerator = require('strict-password-generator').default; +/** + * Services for server administration. + */ export class GBAdminService { - public static GB_PROMPT: string = 'GeneralBots: '; public static masterBotInstanceId = 0; - public static StrongRegex = new RegExp( - '^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*+_-])(?=.{8,})' - ); + public static StrongRegex = new RegExp('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*+_-])(?=.{8,})'); public core: IGBCoreService; @@ -62,26 +63,16 @@ export class GBAdminService { return msRestAzure.generateUuid(); } - public static async getADALTokenFromUsername( - username: string, - password: string - ) { - const credentials = await GBAdminService.getADALCredentialsFromUsername( - username, - password - ); + public static async getADALTokenFromUsername(username: string, password: string) { + const credentials = await GBAdminService.getADALCredentialsFromUsername(username, password); const accessToken = credentials.tokenCache._entries[0].accessToken; + return accessToken; } - public static async getADALCredentialsFromUsername( - username: string, - password: string - ) { - const credentials = await msRestAzure.loginWithUsernamePassword( - username, - password - ); + public static async getADALCredentialsFromUsername(username: string, password: string) { + const credentials = await msRestAzure.loginWithUsernamePassword(username, password); + return credentials; } @@ -114,31 +105,47 @@ export class GBAdminService { return name; } - public async setValue( - instanceId: number, - key: string, - value: string - ): Promise { + public async setValue(instanceId: number, key: string, value: string): Promise { const options = { where: {} }; options.where = { key: key }; let admin = await GuaribasAdmin.findOne(options); - if (admin == null) { + if (admin === null) { admin = new GuaribasAdmin(); admin.key = key; } admin.value = value; admin.instanceId = instanceId; + return admin.save(); } + public async updateSecurityInfo( + instanceId: number, + authenticatorTenant: string, + authenticatorAuthorityHostUrl: string, + authenticatorClientId: string, + authenticatorClientSecret: string + ): Promise { + const options = { where: {} }; + options.where = { instanceId: instanceId }; + const item = await GuaribasInstance.findOne(options); + item.authenticatorTenant = authenticatorTenant; + item.authenticatorAuthorityHostUrl = authenticatorAuthorityHostUrl; + item.authenticatorClientId = authenticatorClientId; + item.authenticatorClientSecret = authenticatorClientSecret; + + return item.save(); + } + public async getValue(instanceId: number, key: string) { const options = { where: {} }; options.where = { key: key, instanceId: instanceId }; const obj = await GuaribasAdmin.findOne(options); + return Promise.resolve(obj.value); } - public async acquireElevatedToken(instanceId): Promise { + public async acquireElevatedToken(instanceId: number): Promise { return new Promise(async (resolve, reject) => { const instance = await this.core.loadInstanceById(instanceId); @@ -166,21 +173,9 @@ export class GBAdminService { reject(err); } else { const token = res as TokenResponse; - await this.setValue( - instanceId, - 'accessToken', - token.accessToken - ); - await this.setValue( - instanceId, - 'refreshToken', - token.refreshToken - ); - await this.setValue( - instanceId, - 'expiresOn', - token.expiresOn.toString() - ); + await this.setValue(instanceId, 'accessToken', token.accessToken); + await this.setValue(instanceId, 'refreshToken', token.refreshToken); + await this.setValue(instanceId, 'expiresOn', token.expiresOn.toString()); resolve(token.accessToken); } } @@ -188,5 +183,4 @@ export class GBAdminService { } }); } - } diff --git a/packages/admin.gbapp/strings.ts b/packages/admin.gbapp/strings.ts index db158f9d..0775e9ed 100644 --- a/packages/admin.gbapp/strings.ts +++ b/packages/admin.gbapp/strings.ts @@ -4,7 +4,7 @@ export const Messages = { welcome: 'Welcome to Pragmatismo.io GeneralBots Administration.', which_task: 'Which task do you wanna run now?', working: (command) => `I'm working on ${command}...`, - finshed_working: 'Done.', + finished_working: 'Done.', unknown_command: text => `Well, but ${text} is not a administrative General Bots command, I will try to search for it.`, hi: text => `Hello, ${text}.`, @@ -13,7 +13,11 @@ export const Messages = { redeployPackage: text => `Redeploying package ${text}...`, packageUndeployed: text => `Package ${text} undeployed...`, consent: (url) => `Please, consent access to this app at: [Microsoft Online](${url}).`, - wrong_password: 'Sorry, wrong password. Please, try again.' + wrong_password: 'Sorry, wrong password. Please, try again.', + enter_authenticator_tenant: 'Enter the Authenticator Tenant (eg.: domain.onmicrosoft.com):', + enter_authenticator_authority_host_url: 'Enter the Authority Host URL (eg.: https://login.microsoftonline.com): ', + enter_authenticator_client_id: 'Enter the Client Id [Application Id](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredAppsPreview) GUID:', + enter_authenticator_client_secret: 'Enter the Client Secret:' }, 'pt-BR': { show_video: 'Vou te mostrar um vĂ­deo. Por favor, aguarde...', diff --git a/packages/azuredeployer.gbapp/index.ts b/packages/azuredeployer.gbapp/index.ts index 93f60ac9..ff4ac32e 100644 --- a/packages/azuredeployer.gbapp/index.ts +++ b/packages/azuredeployer.gbapp/index.ts @@ -36,11 +36,11 @@ 'use strict'; -const UrlJoin = require('url-join'); import { GBMinInstance, IGBCoreService, IGBPackage } from 'botlib'; import { Sequelize } from 'sequelize-typescript'; -export class GBWhatsappPackage implements IGBPackage { +export class GBAzureDeployerPackage implements IGBPackage { + public sysPackages: IGBPackage[] = null; public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {} diff --git a/packages/azuredeployer.gbapp/services/AzureDeployerService.ts b/packages/azuredeployer.gbapp/services/AzureDeployerService.ts index b271e795..8d47ca97 100644 --- a/packages/azuredeployer.gbapp/services/AzureDeployerService.ts +++ b/packages/azuredeployer.gbapp/services/AzureDeployerService.ts @@ -224,7 +224,7 @@ export class AzureDeployerService extends GBService { logger.info(`Deploying Bot Storage...`); const administratorLogin = `sa${GBAdminService.getRndReadableIdentifier()}`; const administratorPassword = GBAdminService.getRndPassword(); - const storageServer = `${name}-storage-server`; + const storageServer = `${name.toLowerCase()}-storage-server`; const storageName = `${name}-storage`; await this.createStorageServer( name, @@ -311,6 +311,11 @@ export class AzureDeployerService extends GBService { instance.nlpAppId = nlpAppId; logger.info(`Deploying Bot...`); + + // TODO: Default endpoint, will be updated when it runs in production. + + instance.botEndpoint = 'http://localhost:4242'; + instance = await this.deployBootBot( instance, name, diff --git a/packages/core.gbapp/services/GBCoreService.ts b/packages/core.gbapp/services/GBCoreService.ts index 9d376dee..cdb036a4 100644 --- a/packages/core.gbapp/services/GBCoreService.ts +++ b/packages/core.gbapp/services/GBCoreService.ts @@ -177,12 +177,11 @@ export class GBCoreService implements IGBCoreService { public async syncDatabaseStructure() { if (GBConfigService.get('STORAGE_SYNC') === 'true') { const alter = GBConfigService.get('STORAGE_SYNC_ALTER') === 'true'; - const force = GBConfigService.get('STORAGE_SYNC_FORCE') === 'true'; logger.info('Syncing database...'); return this.sequelize.sync({ alter: alter, - force: force + force: false // Keep it false this due to data loss danger. }); } else { const msg = `Database synchronization is disabled.`; diff --git a/packages/core.gbapp/services/GBDeployer.ts b/packages/core.gbapp/services/GBDeployer.ts index 87cc8c91..cbed900d 100644 --- a/packages/core.gbapp/services/GBDeployer.ts +++ b/packages/core.gbapp/services/GBDeployer.ts @@ -43,19 +43,25 @@ const Fs = require('fs'); const WaitUntil = require('wait-until'); const express = require('express'); const child_process = require('child_process'); +const graph = require('@microsoft/microsoft-graph-client'); import { GBMinInstance, IGBCoreService, IGBInstance } from 'botlib'; import { GBError, IGBPackage } from 'botlib'; import { AzureSearch } from 'pragmatismo-io-framework'; import { AzureDeployerService } from '../../azuredeployer.gbapp/services/AzureDeployerService'; import { GuaribasInstance, GuaribasPackage } from '../models/GBModel'; +import { GBAdminService } from './../../admin.gbapp/services/GBAdminService'; import { KBService } from './../../kb.gbapp/services/KBService'; import { GBConfigService } from './GBConfigService'; import { GBCoreService } from './GBCoreService'; import { GBImporter } from './GBImporterService'; import { GBVMService } from './GBVMService'; -/** Deployer service for bots, themes, ai and more. */ +/** + * + * Deployer service for bots, themes, ai and more. + */ + export class GBDeployer { public static deployFolder = 'packages'; public core: IGBCoreService; @@ -170,6 +176,25 @@ export class GBDeployer { }); } + public async deployFromSharePoint(instanceId: number, path: string) { + const adminService = new GBAdminService(this.core); + const accessToken = adminService.acquireElevatedToken(instanceId); + + // Initialize Graph client. + + const client = graph.Client.init({ + authProvider: done => { + done(null, accessToken); + } + }); + + const events = await client + .api('/me/events') + .select('subject,organizer,start,end') + .orderby('createdDateTime DESC') + .get(); + } + public deployScriptToStorage(instanceId: number, localPath: string) {} public deployTheme(localPath: string) { @@ -184,6 +209,8 @@ export class GBDeployer { // }) } + public async deployPackageFromSharePoint(min: GBMinInstance, path: string) {} + public async deployPackageFromLocalPath(min: GBMinInstance, localPath: string) { const packageType = Path.extname(localPath); @@ -389,24 +416,22 @@ export class GBDeployer { try { child_process.execSync(Path.join(e, 'node_modules/.bin/tsc'), { cwd: e }); import(e) - .then(m => { - const p = new m.Package(); - p.loadPackage(core, core.sequelize); - appPackages.push(p); - logger.info(`App (.gbapp) deployed: ${e}.`); - appPackagesProcessed++; - }) - .catch(err => { - logger.error(`Error deploying .gbapp package: ${e}\n${err}`); - appPackagesProcessed++; - }); - + .then(m => { + const p = new m.Package(); + p.loadPackage(core, core.sequelize); + appPackages.push(p); + logger.info(`App (.gbapp) deployed: ${e}.`); + appPackagesProcessed++; + }) + .catch(err => { + logger.error(`Error deploying .gbapp package: ${e}\n${err}`); + appPackagesProcessed++; + }); } catch (error) { logger.error(`Error compiling .gbapp package ${e}:\n${error.stdout.toString()}`); appPackagesProcessed++; } } - } else { appPackagesProcessed++; } diff --git a/packages/core.gbapp/services/GBMinService.ts b/packages/core.gbapp/services/GBMinService.ts index e5684a5a..f0ad8c7e 100644 --- a/packages/core.gbapp/services/GBMinService.ts +++ b/packages/core.gbapp/services/GBMinService.ts @@ -56,6 +56,7 @@ import { GuaribasInstance } from '../models/GBModel'; import { Messages } from '../strings'; import { GBAdminPackage } from './../../admin.gbapp/index'; import { GBDeployer } from './GBDeployer'; +import { ConfirmPrompt } from 'botbuilder-dialogs'; /** Minimal service layer for a bot. */ @@ -193,7 +194,7 @@ export class GBMinService { ); authorizationUrl = `${authorizationUrl}?response_type=code&client_id=${ min.instance.authenticatorClientId - }&redirect_uri=${min.instance.botEndpoint}/${min.instance.botId}/token`; + }&redirect_uri=${UrlJoin(min.instance.botEndpoint, min.instance.botId, 'token')}`; res.redirect(authorizationUrl); }); @@ -321,6 +322,7 @@ export class GBMinService { min.dialogs = new DialogSet(dialogState); min.dialogs.add(new TextPrompt('textPrompt')); + min.dialogs.add(new ConfirmPrompt('confirmPrompt')); return { min, adapter, conversationState }; } diff --git a/packages/core.gbapp/services/GBVMService.ts b/packages/core.gbapp/services/GBVMService.ts index 30cd2c8d..f7d56861 100644 --- a/packages/core.gbapp/services/GBVMService.ts +++ b/packages/core.gbapp/services/GBVMService.ts @@ -32,12 +32,12 @@ 'use strict'; +import { WaterfallDialog } from 'botbuilder-dialogs'; import { GBMinInstance, IGBCoreService } from 'botlib'; import * as fs from 'fs'; import { DialogClass } from './GBAPIService'; import { GBDeployer } from './GBDeployer'; import { TSCompiler } from './TSCompiler'; -import { WaterfallDialog } from 'botbuilder-dialogs'; const util = require('util'); const logger = require('../../../src/logger'); const vm = require('vm'); @@ -46,10 +46,14 @@ const vb2ts = require('vbscript-to-typescript/dist/converter'); /** * @fileoverview Virtualization services for emulation of BASIC. + * This alpha version is using a converter to translate BASIC to TS + * and string replacements to emulate await code. + * http://stevehanov.ca/blog/index.php?id=92 should be used to run it without + * translation and enhance classic BASIC experience. */ export class GBVMService implements IGBCoreService { - private script = new vm.Script(); + private readonly script = new vm.Script(); public async loadJS( filename: string, @@ -82,7 +86,7 @@ export class GBVMService implements IGBCoreService { // Run JS into the GB context. const jsfile = `bot.js`; - let localPath = UrlJoin(path, jsfile); + const localPath = UrlJoin(path, jsfile); if (fs.existsSync(localPath)) { let code: string = fs.readFileSync(localPath, 'utf8'); @@ -91,12 +95,11 @@ export class GBVMService implements IGBCoreService { // Finds all hear calls. let parsedCode = code; - let hearExp = /(\w+).*hear.*\(\)/; + const hearExp = /(\w+).*hear.*\(\)/; let match1; while ((match1 = hearExp.exec(code))) { - let pos = 0; // Writes async body. @@ -110,7 +113,7 @@ export class GBVMService implements IGBCoreService { pos = pos + match1.index; let tempCode = code.substring(pos + match1[0].length + 1); - let start = pos; + const start = pos; // Balances code blocks and checks for exits. @@ -143,15 +146,15 @@ export class GBVMService implements IGBCoreService { code = parsedCode; } - parsedCode = parsedCode.replace(/("[^"]*"|'[^']*')|\btalk\b/g, function($0, $1) { + parsedCode = parsedCode.replace(/("[^"]*"|'[^']*')|\btalk\b/g, ($0, $1) => { return $1 == undefined ? 'this.talk' : $1; }); - parsedCode = parsedCode.replace(/("[^"]*"|'[^']*')|\bhear\b/g, function($0, $1) { + parsedCode = parsedCode.replace(/("[^"]*"|'[^']*')|\bhear\b/g, ($0, $1) => { return $1 == undefined ? 'this.hear' : $1; }); - parsedCode = parsedCode.replace(/("[^"]*"|'[^']*')|\bsendEmail\b/g, function($0, $1) { + parsedCode = parsedCode.replace(/("[^"]*"|'[^']*')|\bsendEmail\b/g, ($0, $1) => { return $1 == undefined ? 'this.sendEmail' : $1; }); @@ -183,8 +186,8 @@ export class GBVMService implements IGBCoreService { const cbId = step.activeDialog.state.cbId; const cb = min.cbMap[cbId]; - cb.bind({ step: step, context: step.context }); // TODO: Necessary or min.sandbox - + cb.bind({ step: step, context: step.context }); // TODO: Necessary or min.sandbox? + await step.endDialog(); return await cb(step.result); diff --git a/tslint.json b/tslint.json index 1b26d002..b23d6bb2 100644 --- a/tslint.json +++ b/tslint.json @@ -36,6 +36,7 @@ "export-name":false, "no-relative-imports": false, "no-backbone-get-set-outside-model": false, - "max-line-length": [true,{"limit":120,"ignore-pattern":"^\\s+\\*"}] + "max-line-length": [true,{"limit":120,"ignore-pattern":"^\\s+\\*"}], + "await-promise": [true, "Bluebird"] } }