diff --git a/package-lock.json b/package-lock.json index 9e5a51be..ea1d9d4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3166,9 +3166,9 @@ } }, "botlib": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/botlib/-/botlib-0.1.19.tgz", - "integrity": "sha512-JssUqK0NVwLTvs2zcl42jpKTsB5ocse27aVnFukV+wGuAjCeZu6HZpaTue1jTCHojMXAH1TSLNQnuCOOTxpw/w==", + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/botlib/-/botlib-0.1.21.tgz", + "integrity": "sha512-AF8Sp+SF3xN/jzOCo7tnqJfGaYkeHP0mJKcfYL0iHYbUuIVsCVtNmIottd1WTzDfKhls+ZoqclSAbzzoC0LS+Q==", "requires": { "async": "2.6.2", "botbuilder": "4.1.7", diff --git a/package.json b/package.json index c8fec51e..ef8cc94e 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "botbuilder-choices": "4.0.0-preview1.2", "botbuilder-dialogs": "4.2.0", "botbuilder-prompts": "4.0.0-preview1.2", - "botlib": "0.1.19", + "botlib": "^0.1.21", "chai": "4.2.0", "child_process": "^1.0.2", "chokidar": "2.1.2", diff --git a/packages/admin.gbapp/dialogs/AdminDialog.ts b/packages/admin.gbapp/dialogs/AdminDialog.ts index c3473f20..724acc5c 100644 --- a/packages/admin.gbapp/dialogs/AdminDialog.ts +++ b/packages/admin.gbapp/dialogs/AdminDialog.ts @@ -38,14 +38,14 @@ const UrlJoin = require('url-join'); import { BotAdapter } from 'botbuilder'; -import { WaterfallDialog, WaterfallStep, WaterfallStepContext } from 'botbuilder-dialogs'; -import { GBMinInstance } from 'botlib'; -import { IGBDialog } from 'botlib'; +import { WaterfallDialog } from 'botbuilder-dialogs'; +import { GBMinInstance, IGBDialog } from 'botlib'; import { GBConfigService } from '../../core.gbapp/services/GBConfigService'; import { GBDeployer } from '../../core.gbapp/services/GBDeployer'; import { GBImporter } from '../../core.gbapp/services/GBImporterService'; import { GBAdminService } from '../services/GBAdminService'; import { Messages } from '../strings'; +import { AzureDeployerService } from '../../azuredeployer.gbapp/services/AzureDeployerService'; /** * Dialogs for administration tasks. @@ -79,7 +79,10 @@ export class AdminDialog extends IGBDialog { } public static async rebuildIndexPackageCommand(min: GBMinInstance, text: string, deployer: GBDeployer) { - await deployer.rebuildIndex(min.instance); + await deployer.rebuildIndex( + min.instance, + new AzureDeployerService(deployer).getKBSearchSchema(min.instance.searchIndex) + ); } public static async addConnectionCommand(min: GBMinInstance, text: any) { @@ -136,7 +139,7 @@ export class AdminDialog extends IGBDialog { if (text === 'quit') { return await step.replaceDialog('/'); } else if (cmdName === 'createFarm') { - await AdminDialog.createFarmCommand(text, deployer); + await AdminDialog.createFarmCommand(text, min); return await step.replaceDialog('/admin', { firstRun: false }); } else if (cmdName === 'deployPackage') { @@ -224,9 +227,9 @@ export class AdminDialog extends IGBDialog { 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( + 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' @@ -234,10 +237,9 @@ export class AdminDialog extends IGBDialog { await step.context.sendActivity(Messages[locale].consent(url)); - return await step.replaceDialog('/ask', {isReturning: true}); + return await step.replaceDialog('/ask', { isReturning: true }); } ]) ); } - } diff --git a/packages/admin.gbapp/index.ts b/packages/admin.gbapp/index.ts index a7e22288..d3c5aef7 100644 --- a/packages/admin.gbapp/index.ts +++ b/packages/admin.gbapp/index.ts @@ -36,15 +36,18 @@ 'use strict'; -import urlJoin = require('url-join'); - import { GBMinInstance, IGBCoreService, IGBPackage } from 'botlib'; import { Sequelize } from 'sequelize-typescript'; import { AdminDialog } from './dialogs/AdminDialog'; import { GuaribasAdmin } from './models/AdminModel'; export class GBAdminPackage implements IGBPackage { - public sysPackages: IGBPackage[] = null; + public sysPackages: IGBPackage[] = undefined; + + public unloadPackage(core: IGBCoreService): void {} + public getDialogs(min: GBMinInstance) {} + public unloadBot(min: GBMinInstance): void {} + public onNewSession(min: GBMinInstance, step: any): void {} public loadPackage(core: IGBCoreService, sequelize: Sequelize): void { core.sequelize.addModels([GuaribasAdmin]); diff --git a/packages/admin.gbapp/services/GBAdminService.ts b/packages/admin.gbapp/services/GBAdminService.ts index 80c2d1f9..b8db0e5e 100644 --- a/packages/admin.gbapp/services/GBAdminService.ts +++ b/packages/admin.gbapp/services/GBAdminService.ts @@ -37,7 +37,7 @@ 'use strict'; import { AuthenticationContext, TokenResponse } from 'adal-node'; -import { IGBCoreService } from 'botlib'; +import { IGBCoreService, IGBAdminService } from 'botlib'; import { GuaribasInstance } from '../../core.gbapp/models/GBModel'; import { GuaribasAdmin } from '../models/AdminModel'; const UrlJoin = require('url-join'); @@ -47,7 +47,7 @@ const PasswordGenerator = require('strict-password-generator').default; /** * Services for server administration. */ -export class GBAdminService { +export class GBAdminService implements IGBAdminService { public static GB_PROMPT: string = 'GeneralBots: '; public static masterBotInstanceId = 0; @@ -76,7 +76,7 @@ export class GBAdminService { return credentials; } - public static getRndPassword() { + public static getRndPassword(): string { const passwordGenerator = new PasswordGenerator(); const options = { upperCaseAlpha: true, @@ -105,7 +105,7 @@ export class GBAdminService { return name; } - public async setValue(instanceId: number, key: string, value: string): Promise { + public async setValue(instanceId: number, key: string, value: string) { const options = { where: {} }; options.where = { key: key }; let admin = await GuaribasAdmin.findOne(options); @@ -115,8 +115,7 @@ export class GBAdminService { } admin.value = value; admin.instanceId = instanceId; - - return admin.save(); + await admin.save(); } public async updateSecurityInfo( diff --git a/packages/analytics.gblib/index.ts b/packages/analytics.gblib/index.ts index 1c29bb77..3e0860b4 100644 --- a/packages/analytics.gblib/index.ts +++ b/packages/analytics.gblib/index.ts @@ -43,8 +43,8 @@ import { GBMinInstance, IGBCoreService, IGBPackage } from 'botlib'; import { Sequelize } from 'sequelize-typescript'; export class GBAnalyticsPackage implements IGBPackage { - public sysPackages: IGBPackage[] = null; - + public sysPackages: IGBPackage[] = undefined; + public getDialogs(min: GBMinInstance) {} public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {} public unloadPackage(core: IGBCoreService): void {} public loadBot(min: GBMinInstance): void {} diff --git a/packages/azuredeployer.gbapp/dialogs/BotFarmDialog.ts b/packages/azuredeployer.gbapp/dialogs/BotFarmDialog.ts index 19a95fb9..96fd56a3 100644 --- a/packages/azuredeployer.gbapp/dialogs/BotFarmDialog.ts +++ b/packages/azuredeployer.gbapp/dialogs/BotFarmDialog.ts @@ -40,6 +40,7 @@ import { BotAdapter } from 'botbuilder'; import { GBMinInstance } from 'botlib'; import { IGBDialog } from 'botlib'; import { Messages } from '../strings'; +import { WaterfallDialog } from 'botlib/node_modules/botbuilder-dialogs'; export class BotFarmDialog extends IGBDialog { /** @@ -49,21 +50,20 @@ export class BotFarmDialog extends IGBDialog { * @param min The minimal bot instance data. */ public static setup(bot: BotAdapter, min: GBMinInstance) { - min.dialogs.add('/createBotFarm', [ + min.dialogs.add(new WaterfallDialog ('/createBotFarm', [ async step => { const locale = step.context.activity.locale; - await step.prompt('choicePrompt', Messages[locale].what_about_me, [ - '1', - '2', - '3', - '4', - '5' - ]); + await step.prompt('choicePrompt', Messages[locale].what_about_me); + + return step.next(); }, async step => { + const locale = step.context.activity.locale; await step.context.sendActivity(Messages[locale].thanks); + + return step.next(); } - ]); + ])); } } diff --git a/packages/azuredeployer.gbapp/dialogs/StartDialog.ts b/packages/azuredeployer.gbapp/dialogs/StartDialog.ts index 35e5adb1..8ceade28 100644 --- a/packages/azuredeployer.gbapp/dialogs/StartDialog.ts +++ b/packages/azuredeployer.gbapp/dialogs/StartDialog.ts @@ -37,18 +37,18 @@ 'use strict'; import { IGBInstance } from 'botlib'; +import { IGBInstallationDeployer } from 'botlib'; import * as fs from 'fs'; import { GBAdminService } from '../../../packages/admin.gbapp/services/GBAdminService'; import { GBConfigService } from '../../../packages/core.gbapp/services/GBConfigService'; -import { AzureDeployerService } from '../services/AzureDeployerService'; -import { GuaribasInstance } from '../../../packages/core.gbapp/models/GBModel'; const scanf = require('scanf'); /** * Handles command-line dialog for getting info for Boot Bot. */ export class StartDialog { - public static async createBaseInstance() { + + public static async createBaseInstance(installationDeployer: IGBInstallationDeployer) { // No .env so asks for cloud credentials to start a new farm. if (!fs.existsSync(`.env`)) { @@ -75,7 +75,7 @@ export class StartDialog { // Connects to the cloud and retrieves subscriptions. const credentials = await GBAdminService.getADALCredentialsFromUsername(username, password); - const list = await AzureDeployerService.getSubscriptions(credentials); + const list = await installationDeployer.getSubscriptions(credentials); let subscriptionId: string; while (subscriptionId === undefined) { @@ -104,9 +104,8 @@ export class StartDialog { process.stdout.write(`${GBAdminService.GB_PROMPT}Thank you. That is enough information.\nNow building farm...`); - // Prepares the first instance on bot farm. - const instance: IGBInstance = {}; + const instance = {}; instance.botId = botId; instance.cloudUsername = username; @@ -223,7 +222,7 @@ generate manually an App ID and App Secret.\n` private static retrieveLocation() { let location = GBConfigService.get('CLOUD_LOCATION'); if (!location) { - process.stdout.write("CLOUD_LOCATION (eg. 'westus'):"); + process.stdout.write('CLOUD_LOCATION (eg. \'westus\'):'); location = scanf('%s'); } diff --git a/packages/azuredeployer.gbapp/index.ts b/packages/azuredeployer.gbapp/index.ts index ff4ac32e..66d85906 100644 --- a/packages/azuredeployer.gbapp/index.ts +++ b/packages/azuredeployer.gbapp/index.ts @@ -40,8 +40,8 @@ import { GBMinInstance, IGBCoreService, IGBPackage } from 'botlib'; import { Sequelize } from 'sequelize-typescript'; export class GBAzureDeployerPackage implements IGBPackage { - - public sysPackages: IGBPackage[] = null; + public sysPackages: IGBPackage[] = undefined; + public getDialogs(min: GBMinInstance) {} public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {} diff --git a/packages/azuredeployer.gbapp/services/AzureDeployerService.ts b/packages/azuredeployer.gbapp/services/AzureDeployerService.ts index 6f9195f3..012e0c95 100644 --- a/packages/azuredeployer.gbapp/services/AzureDeployerService.ts +++ b/packages/azuredeployer.gbapp/services/AzureDeployerService.ts @@ -43,33 +43,29 @@ import { SearchManagementClient } from 'azure-arm-search'; import { SqlManagementClient } from 'azure-arm-sql'; import { WebSiteManagementClient } from 'azure-arm-website'; import { AppServicePlan } from 'azure-arm-website/lib/models'; -import { GBService, IGBInstance } from 'botlib'; +import { IGBInstance, IGBInstallationDeployer } from 'botlib'; import { HttpMethods, ServiceClient, WebResource } from 'ms-rest-js'; -import { GBDeployer } from '../../../packages/core.gbapp/services/GBDeployer'; -import * as simplegit from 'simple-git/promise'; import { GBAdminService } from '../../../packages/admin.gbapp/services/GBAdminService'; import { GBCorePackage } from '../../../packages/core.gbapp'; import { GBConfigService } from '../../../packages/core.gbapp/services/GBConfigService'; -import { GuaribasInstance } from '../../../packages/core.gbapp/models/GBModel'; +import { GBDeployer } from '../../../packages/core.gbapp/services/GBDeployer'; const Spinner = require('cli-spinner').Spinner; -const scanf = require('scanf'); -const git = simplegit(); const logger = require('../../../src/logger'); const UrlJoin = require('url-join'); const iconUrl = 'https://github.com/pragmatismo-io/BotServer/blob/master/docs/images/generalbots-logo-squared.png'; const publicIp = require('public-ip'); -export class AzureDeployerService extends GBService { - public static apiVersion = '2017-12-01'; - public static defaultEndPoint = 'http://localhost:4242'; +export class AzureDeployerService implements IGBInstallationDeployer { + public apiVersion = '2017-12-01'; + public defaultEndPoint = 'http://localhost:4242'; public instance: IGBInstance; public resourceClient: ResourceManagementClient.ResourceManagementClient; public webSiteClient: WebSiteManagementClient; public storageClient: SqlManagementClient; public cognitiveClient: CognitiveServicesManagementClient; public searchClient: SearchManagementClient; - public static provider = 'Microsoft.BotService'; + public provider = 'Microsoft.BotService'; public subscriptionClient: SubscriptionClient.SubscriptionClient; public accessToken: string; public location: string; @@ -78,15 +74,16 @@ export class AzureDeployerService extends GBService { public deployer: GBDeployer; constructor(deployer: GBDeployer) { - super(); this.deployer = deployer; } - public static async getSubscriptions(credentials) { + + public async getSubscriptions(credentials) { const subscriptionClient = new SubscriptionClient.default(credentials); + return subscriptionClient.subscriptions.list(); } - public static getKBSearchSchema(indexName) { + public getKBSearchSchema(indexName) { return { name: indexName, fields: [ @@ -187,16 +184,13 @@ export class AzureDeployerService extends GBService { }; } - public static async updateBotProxy(botId, group, endpoint) { + public async updateBotProxy(botId, group, endpoint) { const baseUrl = `https://management.azure.com/`; const username = GBConfigService.get('CLOUD_USERNAME'); const password = GBConfigService.get('CLOUD_PASSWORD'); const subscriptionId = GBConfigService.get('CLOUD_SUBSCRIPTIONID'); - const accessToken = await GBAdminService.getADALTokenFromUsername( - username, - password - ); + const accessToken = await GBAdminService.getADALTokenFromUsername(username, password); const httpClient = new ServiceClient(); const parameters = { @@ -207,14 +201,9 @@ export class AzureDeployerService extends GBService { const query = `subscriptions/${subscriptionId}/resourceGroups/${group}/providers/${ this.provider - }/botServices/${botId}?api-version=${AzureDeployerService.apiVersion}`; + }/botServices/${botId}?api-version=${this.apiVersion}`; const url = UrlJoin(baseUrl, query); - const req = this.createRequestObject( - url, - accessToken, - 'PATCH', - JSON.stringify(parameters) - ); + const req = AzureDeployerService.createRequestObject(url, accessToken, 'PATCH', JSON.stringify(parameters)); const res = await httpClient.sendRequest(req); if (!(res.bodyAsJson as any).id) { throw res.bodyAsText; @@ -222,7 +211,7 @@ export class AzureDeployerService extends GBService { logger.info(`Bot proxy updated at: ${endpoint}.`); } - public static async openStorageFirewall(groupName, serverName) { + public async openStorageFirewall(groupName, serverName) { const username = GBConfigService.get('CLOUD_USERNAME'); const password = GBConfigService.get('CLOUD_PASSWORD'); const subscriptionId = GBConfigService.get('CLOUD_SUBSCRIPTIONID'); @@ -239,7 +228,7 @@ export class AzureDeployerService extends GBService { await storageClient.firewallRules.createOrUpdate(groupName, serverName, 'gb', params); } - public static createRequestObject(url: string, accessToken: string, verb: HttpMethods, body: string) { + private static createRequestObject(url: string, accessToken: string, verb: HttpMethods, body: string) { const req = new WebResource(); req.method = verb; req.url = url; @@ -304,19 +293,19 @@ export class AzureDeployerService extends GBService { instance.searchIndex = 'azuresql-index'; instance.searchIndexer = 'azuresql-indexer'; instance.searchKey = searchKeys.primaryKey; - this.deployer.rebuildIndex(instance); + this.deployer.rebuildIndex(instance, this.deployer); logger.info(`Deploying Speech...`); const speech = await this.createSpeech(name, `${name}-speech`, instance.cloudLocation); keys = await this.cognitiveClient.accounts.listKeys(name, speech.name); - instance.speechKeyEndpoint = speech.endpoint; + instance.speechEndpoint = speech.endpoint; instance.speechKey = keys.key1; logger.info(`Deploying SpellChecker...`); - const spellChecker = await this.createSpellChecker(name, `${name}-spellchecker`, instance.cloudLocation); + const spellChecker = await this.createSpellChecker(name, `${name}-spellchecker`); keys = await this.cognitiveClient.accounts.listKeys(name, spellChecker.name); - instance.spellCheckerKey = keys.key1; - instance.spellCheckerEndpoint = spellChecker.endpoint; + instance.spellcheckerKey = keys.key1; + instance.spellcheckerEndpoint = spellChecker.endpoint; logger.info(`Deploying Text Analytics...`); const textAnalytics = await this.createTextAnalytics(name, `${name}-textanalytics`, instance.cloudLocation); @@ -336,7 +325,7 @@ export class AzureDeployerService extends GBService { instance.nlpAppId = nlpAppId; logger.info(`Deploying Bot...`); - instance.botEndpoint = AzureDeployerService.defaultEndPoint; + instance.botEndpoint = this.defaultEndPoint; instance = await this.internalDeployBot( instance, @@ -349,8 +338,8 @@ export class AzureDeployerService extends GBService { 'global', instance.nlpAppId, instance.nlpKey, - instance.appId, - instance.appPassword, + instance.marketplaceId, + instance.marketplacePassword, instance.cloudSubscriptionId ); @@ -359,17 +348,16 @@ export class AzureDeployerService extends GBService { } public async deployToCloud( - title, - username, - password, - cloudLocation, - authoringKey, - appId, - appPassword, - subscriptionId + title: string, + username: string, + password: string, + cloudLocation: string, + authoringKey: string, + appId: string, + appPassword: string, + subscriptionId: string ) { - - const instance: IGBInstance = {}; + const instance = {}; instance.botId = title; instance.cloudUsername = username; @@ -397,18 +385,6 @@ export class AzureDeployerService extends GBService { this.accessToken = credentials.tokenCache._entries[0].accessToken; } - private async updateWebisteConfig(group, serverFarmId, name, location) { - const siteConfig = { - location: location, - serverFarmId: serverFarmId, - numberOfWorkers: 1, - phpVersion: '5.5' - }; - - // TODO: Copy .env to app settings. - - return this.webSiteClient.webApps.createOrUpdateConfiguration(group, name, siteConfig); - } private async createStorageServer(group, name, administratorLogin, administratorPassword, serverName, location) { const params = { @@ -423,7 +399,7 @@ export class AzureDeployerService extends GBService { private async registerProviders(subscriptionId, baseUrl, accessToken) { const query = `subscriptions/${subscriptionId}/providers/${ - AzureDeployerService.provider + this.provider }/register?api-version=2018-02-01`; const requestUrl = UrlJoin(baseUrl, query); @@ -436,7 +412,6 @@ export class AzureDeployerService extends GBService { req.headers.Authorization = 'Bearer ' + accessToken; const httpClient = new ServiceClient(); - const res = await httpClient.sendRequest(req); // TODO: Check res for error. } @@ -457,7 +432,7 @@ export class AzureDeployerService extends GBService { appId, appPassword, subscriptionId - ) { + ): Promise { return new Promise(async (resolve, reject) => { const baseUrl = `https://management.azure.com/`; await this.registerProviders(subscriptionId, baseUrl, accessToken); @@ -489,8 +464,8 @@ export class AzureDeployerService extends GBService { const httpClient = new ServiceClient(); let query = `subscriptions/${subscriptionId}/resourceGroups/${group}/providers/${ - AzureDeployerService.provider - }/botServices/${botId}?api-version=${AzureDeployerService.apiVersion}`; + this.provider + }/botServices/${botId}?api-version=${this.apiVersion}`; let url = UrlJoin(baseUrl, query); let req = AzureDeployerService.createRequestObject(url, accessToken, 'PUT', JSON.stringify(parameters)); const res = await httpClient.sendRequest(req); @@ -502,7 +477,7 @@ export class AzureDeployerService extends GBService { setTimeout(async () => { try { query = `subscriptions/${subscriptionId}/resourceGroups/${group}/providers/Microsoft.BotService/botServices/${botId}/channels/WebChatChannel/listChannelWithKeys?api-version=${ - AzureDeployerService.apiVersion + this.apiVersion }`; url = UrlJoin(baseUrl, query); req = AzureDeployerService.createRequestObject(url, accessToken, 'GET', JSON.stringify(parameters)); @@ -513,7 +488,7 @@ export class AzureDeployerService extends GBService { } catch (error) { reject(error); } - }, 20000); + }, 20000); }); } @@ -603,7 +578,7 @@ export class AzureDeployerService extends GBService { return await this.createCognitiveServices(group, name, location, 'LUIS'); } - private async createSpellChecker(group, name, location): Promise { + private async createSpellChecker(group, name): Promise { return await this.createCognitiveServices(group, name, 'global', 'Bing.SpellCheck.v7'); } @@ -637,5 +612,4 @@ export class AzureDeployerService extends GBService { }; return this.webSiteClient.webApps.createOrUpdate(group, name, parameters); } - } diff --git a/packages/console.gblib/index.ts b/packages/console.gblib/index.ts index 09eb4489..e056b050 100644 --- a/packages/console.gblib/index.ts +++ b/packages/console.gblib/index.ts @@ -44,22 +44,20 @@ import { Sequelize } from 'sequelize-typescript'; import { ConsoleDirectLine } from './services/ConsoleDirectLine'; export class GBConsolePackage implements IGBPackage { - public sysPackages: IGBPackage[] = null; + + public sysPackages: IGBPackage[] = undefined; public channel: ConsoleDirectLine; + public getDialogs(min: GBMinInstance) {} - public loadPackage(core: IGBCoreService, sequelize: Sequelize): void { - } + public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {} - public unloadPackage(core: IGBCoreService): void { - } + public unloadPackage(core: IGBCoreService): void {} public loadBot(min: GBMinInstance): void { this.channel = new ConsoleDirectLine(min.instance.webchatKey); } - public unloadBot(min: GBMinInstance): void { - } + public unloadBot(min: GBMinInstance): void {} - public onNewSession(min: GBMinInstance, step: any): void { - } + public onNewSession(min: GBMinInstance, step: any): void {} } diff --git a/packages/core.gbapp/index.ts b/packages/core.gbapp/index.ts index 0ea3b719..7166bfa6 100644 --- a/packages/core.gbapp/index.ts +++ b/packages/core.gbapp/index.ts @@ -40,7 +40,7 @@ const UrlJoin = require('url-join'); import { GBMinInstance, IGBPackage } from 'botlib'; -import { IGBCoreService} from 'botlib'; +import { IGBCoreService } from 'botlib'; import { Sequelize } from 'sequelize-typescript'; import { WelcomeDialog } from './dialogs/WelcomeDialog'; import { WhoAmIDialog } from './dialogs/WhoAmIDialog'; @@ -48,30 +48,20 @@ import { GuaribasChannel, GuaribasException, GuaribasInstance, GuaribasPackage } export class GBCorePackage implements IGBPackage { public static CurrentEngineName = 'guaribas-1.0.0'; - public sysPackages: IGBPackage[] = null; + public sysPackages: IGBPackage[] = undefined; + public getDialogs(min: GBMinInstance) {} public loadPackage(core: IGBCoreService, sequelize: Sequelize): void { - core.sequelize.addModels([ - GuaribasInstance, - GuaribasPackage, - GuaribasChannel, - GuaribasException - ]); + core.sequelize.addModels([GuaribasInstance, GuaribasPackage, GuaribasChannel, GuaribasException]); } - public unloadPackage(core: IGBCoreService): void { - - } + public unloadPackage(core: IGBCoreService): void {} public loadBot(min: GBMinInstance): void { WelcomeDialog.setup(min.bot, min); WhoAmIDialog.setup(min.bot, min); } - public unloadBot(min: GBMinInstance): void { - - } - public onNewSession(min: GBMinInstance, step: any): void { - - } + public unloadBot(min: GBMinInstance): void {} + public onNewSession(min: GBMinInstance, step: any): void {} } diff --git a/packages/core.gbapp/models/GBModel.ts b/packages/core.gbapp/models/GBModel.ts index ca0bf05b..a382b92b 100644 --- a/packages/core.gbapp/models/GBModel.ts +++ b/packages/core.gbapp/models/GBModel.ts @@ -54,6 +54,7 @@ import { IGBInstance } from 'botlib'; @Table export class GuaribasInstance extends Model implements IGBInstance { + @PrimaryKey @AutoIncrement @Column @@ -152,7 +153,7 @@ export class GuaribasInstance extends Model public speechKey: string; @Column - public speechKeyEndpoint: string; + public speechEndpoint: string; @Column public spellcheckerKey: string; diff --git a/packages/core.gbapp/services/GBAPIService.ts b/packages/core.gbapp/services/GBAPIService.ts index 23335bfc..dccd0f39 100644 --- a/packages/core.gbapp/services/GBAPIService.ts +++ b/packages/core.gbapp/services/GBAPIService.ts @@ -35,22 +35,27 @@ import { TurnContext } from 'botbuilder'; import { WaterfallStepContext } from 'botbuilder-dialogs'; import { GBMinInstance } from 'botlib'; +import * as request from 'request-promise-native'; import { GBAdminService } from '../../admin.gbapp/services/GBAdminService'; import { AzureDeployerService } from '../../azuredeployer.gbapp/services/AzureDeployerService'; - +import { GBDeployer } from './GBDeployer'; +const UrlJoin = require('url-join'); /** * BASIC system class for extra manipulation of bot behaviour. */ class SysClass { public min: GBMinInstance; + private readonly deployer: GBDeployer; - constructor(min: GBMinInstance) { + constructor(min: GBMinInstance, deployer: GBDeployer) { this.min = min; + this.deployer = deployer; } public async wait(seconds: number) { - const timeout = ms => new Promise(resolve => setTimeout(resolve, ms)); + // tslint:disable-next-line no-string-based-set-timeout + const timeout = async (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); await timeout(seconds * 1000); } @@ -68,7 +73,7 @@ class SysClass { appPassword, subscriptionId ) { - const service = new AzureDeployerService(this.min.deployer); + const service = new AzureDeployerService(this.deployer); await service.deployToCloud( botId, username, @@ -80,6 +85,27 @@ class SysClass { subscriptionId ); } + + /** + * Generic function to call any REST API. + */ + public async sendEmail(to, subject, body) { + // tslint:disable-next-line:no-console + console.log(`[E-mail]: to:${to}, subject: ${subject}, body: ${body}.`); + } + + /** + * Generic function to call any REST API. + */ + public async httpGet(url: string, qs) { + + const options = { + uri: UrlJoin(url , qs) + }; + + return await request.get(options); + } + } /** * @fileoverview General Bots server core. @@ -92,9 +118,9 @@ export default class DialogClass { public step: WaterfallStepContext; public internalSys: SysClass; - constructor(min: GBMinInstance) { + constructor(min: GBMinInstance, deployer: GBDeployer) { this.min = min; - this.internalSys = new SysClass(min); + this.internalSys = new SysClass(min, deployer); } public sys(): SysClass { @@ -102,7 +128,7 @@ export default class DialogClass { } public async hear(cb) { - const idCallback = Math.floor(Math.random() * 1000000000000); + const idCallback = crypto.getRandomValues(new Uint32Array(16))[0]; this.min.cbMap[idCallback] = cb; await this.step.beginDialog('/hear', { id: idCallback }); } @@ -110,17 +136,4 @@ export default class DialogClass { public async talk(text: string) { return await this.context.sendActivity(text); } - - /** - * Generic function to call any REST API. - */ - public sendEmail(to, subject, body) { - // tslint:disable-next-line:no-console - console.log(`[E-mail]: to:${to}, subject: ${subject}, body: ${body}.`); - } - - /** - * Generic function to call any REST API. - */ - public post(url: string, data) {} } diff --git a/packages/core.gbapp/services/GBConversationalService.ts b/packages/core.gbapp/services/GBConversationalService.ts index 9ea23c35..3c83c608 100644 --- a/packages/core.gbapp/services/GBConversationalService.ts +++ b/packages/core.gbapp/services/GBConversationalService.ts @@ -39,7 +39,7 @@ const logger = require('../../../src/logger'); import { MessageFactory } from 'botbuilder'; import { LuisRecognizer } from 'botbuilder-ai'; -import { GBMinInstance, IGBConversationalService } from 'botlib'; +import { GBMinInstance, IGBConversationalService, IGBCoreService } from 'botlib'; import { AzureText } from 'pragmatismo-io-framework'; import { Messages } from '../strings'; import { GBCoreService } from './GBCoreService'; @@ -51,9 +51,10 @@ export interface LanguagePickerSettings { } export class GBConversationalService implements IGBConversationalService { - public coreService: GBCoreService; - constructor(coreService: GBCoreService) { + public coreService: IGBCoreService; + + constructor(coreService: IGBCoreService) { this.coreService = coreService; } @@ -93,7 +94,7 @@ export class GBConversationalService implements IGBConversationalService { public async routeNLP(step: any, min: GBMinInstance, text: string): Promise { // Invokes LUIS. - let endpoint = min.instance.nlpEndpoint.replace('/luis/v2.0', ''); + const endpoint = min.instance.nlpEndpoint.replace('/luis/v2.0', ''); const model = new LuisRecognizer({ applicationId: min.instance.nlpAppId, diff --git a/packages/core.gbapp/services/GBCoreService.ts b/packages/core.gbapp/services/GBCoreService.ts index bfc9a9ef..eefef487 100644 --- a/packages/core.gbapp/services/GBCoreService.ts +++ b/packages/core.gbapp/services/GBCoreService.ts @@ -36,12 +36,13 @@ 'use strict'; -import { IGBCoreService, IGBInstance, IGBPackage } from 'botlib'; +import { IGBCoreService, IGBInstallationDeployer, IGBInstance, IGBPackage } from 'botlib'; import * as fs from 'fs'; import { Sequelize } from 'sequelize-typescript'; import { GBAdminPackage } from '../../admin.gbapp/index'; import { GBAdminService } from '../../admin.gbapp/services/GBAdminService'; import { GBAnalyticsPackage } from '../../analytics.gblib'; +import { StartDialog } from '../../azuredeployer.gbapp/dialogs/StartDialog'; import { AzureDeployerService } from '../../azuredeployer.gbapp/services/AzureDeployerService'; import { GBCorePackage } from '../../core.gbapp'; import { GBCustomerSatisfactionPackage } from '../../customer-satisfaction.gbapp'; @@ -50,8 +51,6 @@ import { GBSecurityPackage } from '../../security.gblib'; import { GBWhatsappPackage } from '../../whatsapp.gblib/index'; import { GuaribasInstance } from '../models/GBModel'; import { GBConfigService } from './GBConfigService'; -import { StartDialog } from '../../azuredeployer.gbapp/dialogs/StartDialog'; -import { WaterfallDialog } from 'botbuilder-dialogs'; const logger = require('../../../src/logger'); const opn = require('opn'); @@ -60,6 +59,7 @@ const opn = require('opn'); * Core service layer. */ export class GBCoreService implements IGBCoreService { + /** * Data access layer instance. */ @@ -162,13 +162,13 @@ export class GBCoreService implements IGBCoreService { } } - public async checkStorage(azureDeployer: AzureDeployerService) { + public async checkStorage(installationDeployer: IGBInstallationDeployer) { try { await this.sequelize.authenticate(); } catch (error) { logger.info('Opening storage firewall on infrastructure...'); if (error.parent.code === 'ELOGIN') { - await this.openStorageFrontier(azureDeployer); + await this.openStorageFrontier(installationDeployer); } else { throw error; } @@ -193,14 +193,14 @@ export class GBCoreService implements IGBCoreService { /** * Loads all items to start several listeners. */ - public async loadInstances(): Promise { + public async loadInstances(): Promise { return GuaribasInstance.findAll({}); } /** * Loads just one Bot instance by its internal Id. */ - public async loadInstanceById(instanceId: string): Promise { + public async loadInstanceById(instanceId: number): Promise { const options = { where: { instanceId: instanceId } }; return GuaribasInstance.findOne(options); @@ -240,8 +240,15 @@ STORAGE_SYNC=true public async ensureProxy(port): Promise { try { - const ngrok = require('ngrok'); - return await ngrok.connect({ port: port }); + if (fs.existsSync('node_modules/ngrok/bin/ngrok.exe')) { + const ngrok = require('ngrok'); + + return await ngrok.connect({ port: port }); + } else { + logger.warn('ngrok executable not found. Check installation or node_modules folder.'); + + return 'localhost'; + } } catch (error) { // There are false positive from ngrok regarding to no memory, but it's just // lack of connection. @@ -267,15 +274,15 @@ STORAGE_SYNC=true * @param azureDeployer * @param proxyAddress */ - public async loadAllInstances(core: GBCoreService, azureDeployer: AzureDeployerService, proxyAddress: string) { + public async loadAllInstances(core: IGBCoreService, installationDeployer: IGBInstallationDeployer, proxyAddress: string) { logger.info(`Loading instances from storage...`); - let instances: GuaribasInstance[]; + let instances: IGBInstance[]; try { instances = await core.loadInstances(); const instance = instances[0]; if (process.env.NODE_ENV === 'development') { logger.info(`Updating bot endpoint to local reverse proxy (ngrok)...`); - await AzureDeployerService.updateBotProxy( + await installationDeployer.updateBotProxy( instance.botId, instance.botId, `${proxyAddress}/api/messages/${instance.botId}` @@ -308,9 +315,9 @@ STORAGE_SYNC=true * @param bootInstance * @param core */ - public async ensureInstances(instances: GuaribasInstance[], bootInstance: any, core: GBCoreService) { + public async ensureInstances(instances: IGBInstance[], bootInstance: any, core: IGBCoreService) { if (!instances) { - const instance: IGBInstance = {}; + const instance = new GuaribasInstance(); await instance.save(); instances = await core.loadInstances(); } @@ -347,11 +354,12 @@ STORAGE_SYNC=true } } - public async createBootInstance(core: GBCoreService, azureDeployer: AzureDeployerService, proxyAddress: string) { + public async createBootInstance(core: GBCoreService, installationDeployer: IGBInstallationDeployer, proxyAddress: string) { + logger.info(`Deploying cognitive infrastructure (on the cloud / on premises)...`); try { - let { instance, credentials, subscriptionId } = await StartDialog.createBaseInstance(); - instance = await azureDeployer.deployFarm(proxyAddress, instance, credentials, subscriptionId); + let { instance, credentials, subscriptionId } = await StartDialog.createBaseInstance(installationDeployer); + instance = await installationDeployer.deployFarm(proxyAddress, instance, credentials, subscriptionId); core.writeEnv(instance); logger.info(`File .env written, starting General Bots...`); GBConfigService.init(); @@ -457,9 +465,9 @@ STORAGE_SYNC=true * * @param azureDeployer Infrastructure Deployer instance. */ - private async openStorageFrontier(deployer: AzureDeployerService) { + private async openStorageFrontier(installationDeployer: IGBInstallationDeployer) { const group = GBConfigService.get('CLOUD_GROUP'); const serverName = GBConfigService.get('STORAGE_SERVER').split('.database.windows.net')[0]; - await AzureDeployerService.openStorageFirewall(group, serverName); + await installationDeployer.openStorageFirewall(group, serverName); } } diff --git a/packages/core.gbapp/services/GBDeployer.ts b/packages/core.gbapp/services/GBDeployer.ts index 1234aa89..b526a48b 100644 --- a/packages/core.gbapp/services/GBDeployer.ts +++ b/packages/core.gbapp/services/GBDeployer.ts @@ -73,7 +73,7 @@ export class GBDeployer { this.importer = importer; } - public static getConnectionStringFromInstance(instance: GuaribasInstance) { + public static getConnectionStringFromInstance(instance: IGBInstance) { return `Server=tcp:${instance.storageServer}.database.windows.net,1433;Database=${instance.storageName};User ID=${ instance.storageUsername };Password=${instance.storagePassword};Trusted_Connection=False;Encrypt=True;Connection Timeout=30;`; @@ -272,7 +272,7 @@ export class GBDeployer { } } - public async rebuildIndex(instance: GuaribasInstance) { + public async rebuildIndex(instance: IGBInstance, searchSchema: any) { const search = new AzureSearch( instance.searchKey, instance.searchHost, @@ -302,7 +302,7 @@ export class GBDeployer { throw err; } } - await search.createIndex(AzureDeployerService.getKBSearchSchema(instance.searchIndex), dsName); + await search.createIndex(searchSchema, dsName); } public async getPackageByName(instanceId: number, packageName: string): Promise { @@ -312,7 +312,7 @@ export class GBDeployer { }); } - public installDefaultGBUI() { + public runOnce() { const root = 'packages/default.gbui'; if (!Fs.existsSync(`${root}/build`)) { logger.info(`Preparing default.gbui (it may take some additional time for the first time)...`); @@ -323,7 +323,7 @@ export class GBDeployer { } private async deployDataPackages( - core: GBCoreService, + core: IGBCoreService, botPackages: string[], _this: this, generalPackages: string[], diff --git a/packages/core.gbapp/services/GBMinService.ts b/packages/core.gbapp/services/GBMinService.ts index 086cb54b..738b942a 100644 --- a/packages/core.gbapp/services/GBMinService.ts +++ b/packages/core.gbapp/services/GBMinService.ts @@ -46,7 +46,7 @@ const AuthenticationContext = require('adal-node').AuthenticationContext; import { AutoSaveStateMiddleware, BotFrameworkAdapter, ConversationState, MemoryStorage, UserState } from 'botbuilder'; import { ConfirmPrompt, WaterfallDialog } from 'botbuilder-dialogs'; -import { GBMinInstance, IGBAdminService, IGBConversationalService, IGBCoreService, IGBPackage } from 'botlib'; +import { GBMinInstance, IGBAdminService, IGBConversationalService, IGBCoreService, IGBPackage, IGBInstance } from 'botlib'; import { GBAnalyticsPackage } from '../../analytics.gblib'; import { GBCorePackage } from '../../core.gbapp'; import { GBCustomerSatisfactionPackage } from '../../customer-satisfaction.gbapp'; @@ -97,12 +97,12 @@ export class GBMinService { * */ public async buildMin( - bootInstance: GuaribasInstance, + bootInstance: IGBInstance, server: any, appPackages: IGBPackage[], - instances: GuaribasInstance[], + instances: IGBInstance[], deployer: GBDeployer - ): Promise { + ) { // Serves default UI on root address '/'. const uiPackage = 'default.gbui'; @@ -166,9 +166,9 @@ export class GBMinService { ); } - private handleOAuthTokenRequests(server: any, min: GBMinInstance, instance: GuaribasInstance) { + private handleOAuthTokenRequests(server: any, min: GBMinInstance, instance: IGBInstance) { server.get(`/${min.instance.botId}/token`, async (req, res) => { - const state = await min.adminService.getValue(min.instance.instanceId, 'AntiCSRFAttackState'); + const state = await min.adminService.getValue(instance.instanceId, 'AntiCSRFAttackState'); if (req.query.state !== state) { const msg = 'WARNING: state field was not provided as anti-CSRF token'; logger.error(msg); @@ -218,7 +218,7 @@ export class GBMinService { /** * Returns the instance object to clients requesting bot info. */ - private async sendInstanceToClient(req, bootInstance: GuaribasInstance, res: any, webchatToken: any) { + private async sendInstanceToClient(req, bootInstance: IGBInstance, res: any, webchatToken: any) { let botId = req.params.botId; if (botId === '[default]') { botId = bootInstance.botId; @@ -351,7 +351,7 @@ export class GBMinService { if (sysPackage.name === 'GBWhatsappPackage') { const url = '/instances/:botId/whatsapp'; server.post(url, (req, res) => { - p.channel.received(req, res); + p['channel'].received(req, res); }); } }, this); @@ -383,7 +383,7 @@ export class GBMinService { await adapter.processActivity(req, res, async 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); step.context.activity.locale = 'en-US'; // TODO: Make dynamic. try { @@ -481,10 +481,10 @@ export class GBMinService { if (isVMCall) { let mainMethod = context.activity.text; - min.sandbox.context = context; - min.sandbox.step = step; - min.sandbox[mainMethod].bind(min.sandbox); - await min.sandbox[mainMethod](); + min.sandBoxMap[mainMethod].context = context; + min.sandBoxMap[mainMethod].step = step; + min.sandBoxMap[mainMethod][mainMethod].bind(min.sandBoxMap[mainMethod]); + await min.sandBoxMap[mainMethod][mainMethod](); } else if (context.activity.text === 'admin') { await step.beginDialog('/admin'); diff --git a/packages/core.gbapp/services/GBVMService.ts b/packages/core.gbapp/services/GBVMService.ts index 0f999824..40a7afff 100644 --- a/packages/core.gbapp/services/GBVMService.ts +++ b/packages/core.gbapp/services/GBVMService.ts @@ -33,10 +33,11 @@ 'use strict'; import { WaterfallDialog } from 'botbuilder-dialogs'; -import { GBMinInstance, IGBCoreService } from 'botlib'; +import { GBMinInstance, IGBCoreService, GBService } from 'botlib'; import * as fs from 'fs'; import { GBDeployer } from './GBDeployer'; import { TSCompiler } from './TSCompiler'; +import GBAPIService from './GBAPIService'; import DialogClass from './GBAPIService'; const walkPromise = require('walk-promise'); @@ -44,7 +45,7 @@ const logger = require('../../../src/logger'); const vm = require('vm'); const UrlJoin = require('url-join'); const vb2ts = require('vbscript-to-typescript/dist/converter'); -var beautify = require('js-beautify').js; +let beautify = require('js-beautify').js; /** * @fileoverview Virtualization services for emulation of BASIC. @@ -55,7 +56,7 @@ var beautify = require('js-beautify').js; * translation and enhance classic BASIC experience. */ -export class GBVMService implements IGBCoreService { +export class GBVMService extends GBService { private readonly script = new vm.Script(); public async loadDialogPackage(folder: string, min: GBMinInstance, core: IGBCoreService, deployer: GBDeployer) { @@ -110,6 +111,10 @@ export class GBVMService implements IGBCoreService { return 'let password = sys().generatePassword()'; }); + code = code.replace(/(get)(\s)(.*)/g, ($0, $1, $2) => { + return `sys().httpGet (${$2})`; + }); + code = code.replace(/(create a bot farm using)(\s)(.*)/g, ($0, $1, $2, $3) => { return `sys().createABotFarmUsing (${$3})`; }); @@ -207,14 +212,14 @@ export class GBVMService implements IGBCoreService { parsedCode = this.handleThisAndAwait(parsedCode); - parsedCode = beautify(parsedCode, { indent_size: 2, space_in_empty_paren: true }) + parsedCode = beautify(parsedCode, { indent_size: 2, space_in_empty_paren: true }); fs.writeFileSync(jsfile, parsedCode); - const sandbox: DialogClass = new DialogClass(min); + const sandbox: DialogClass = new DialogClass(min, deployer); const context = vm.createContext(sandbox); vm.runInContext(parsedCode, context); - min.sandbox = sandbox; - await deployer.deployScriptToStorage(min.instanceId, filename); + min.sandBoxMap[mainName] = sandbox; + await deployer.deployScriptToStorage(1, filename); // TODO: Per bot storage. logger.info(`[GBVMService] Finished loading of ${filename}`); } } diff --git a/packages/customer-satisfaction.gbapp/index.ts b/packages/customer-satisfaction.gbapp/index.ts index 5324b258..33782cf6 100644 --- a/packages/customer-satisfaction.gbapp/index.ts +++ b/packages/customer-satisfaction.gbapp/index.ts @@ -45,23 +45,17 @@ import { GuaribasQuestionAlternate } from './models/index'; import { Sequelize } from 'sequelize-typescript'; export class GBCustomerSatisfactionPackage implements IGBPackage { - public sysPackages: IGBPackage[] = null; - public loadPackage(core: IGBCoreService, sequelize: Sequelize): void { - core.sequelize.addModels([ - GuaribasQuestionAlternate - ]); - } - public unloadPackage(core: IGBCoreService): void { + public sysPackages: IGBPackage[] = undefined; + public getDialogs(min: GBMinInstance) {} + public loadPackage(core: IGBCoreService, sequelize: Sequelize): void { + core.sequelize.addModels([GuaribasQuestionAlternate]); } + public unloadPackage(core: IGBCoreService): void {} public loadBot(min: GBMinInstance): void { FeedbackDialog.setup(min.bot, min); QualityDialog.setup(min.bot, min); } - public unloadBot(min: GBMinInstance): void { - - } - public onNewSession(min: GBMinInstance, step: any): void { - - } + public unloadBot(min: GBMinInstance): void {} + public onNewSession(min: GBMinInstance, step: any): void {} } diff --git a/packages/kb.gbapp/index.ts b/packages/kb.gbapp/index.ts index da3628c0..9eba17b8 100644 --- a/packages/kb.gbapp/index.ts +++ b/packages/kb.gbapp/index.ts @@ -48,31 +48,18 @@ import { FaqDialog } from './dialogs/FaqDialog'; import { MenuDialog } from './dialogs/MenuDialog'; export class GBKBPackage implements IGBPackage { - - public sysPackages: IGBPackage[] = null; + public sysPackages: IGBPackage[] = undefined; + public getDialogs(min: GBMinInstance) {} public loadPackage(core: IGBCoreService, sequelize: Sequelize): void { - core.sequelize.addModels([ - GuaribasAnswer, - GuaribasQuestion, - GuaribasSubject - ]); - - } - public unloadPackage(core: IGBCoreService): void { - + core.sequelize.addModels([GuaribasAnswer, GuaribasQuestion, GuaribasSubject]); } + public unloadPackage(core: IGBCoreService): void {} public loadBot(min: GBMinInstance): void { - AskDialog.setup(min.bot, min); FaqDialog.setup(min.bot, min); MenuDialog.setup(min.bot, min); - - } - public unloadBot(min: GBMinInstance): void { - - } - public onNewSession(min: GBMinInstance, step: any): void { - } + public unloadBot(min: GBMinInstance): void {} + public onNewSession(min: GBMinInstance, step: any): void {} } diff --git a/packages/kb.gbapp/services/KBService.ts b/packages/kb.gbapp/services/KBService.ts index 231dd7f8..fc47a0e4 100644 --- a/packages/kb.gbapp/services/KBService.ts +++ b/packages/kb.gbapp/services/KBService.ts @@ -53,6 +53,7 @@ import { GuaribasPackage } from '../../core.gbapp/models/GBModel'; import { GBDeployer } from '../../core.gbapp/services/GBDeployer'; import { GuaribasAnswer, GuaribasQuestion, GuaribasSubject } from '../models'; import { GBConfigService } from './../../core.gbapp/services/GBConfigService'; +import { AzureDeployerService } from '../../azuredeployer.gbapp/services/AzureDeployerService'; export class KBServiceSearchResults { public answer: GuaribasAnswer; @@ -326,7 +327,7 @@ export class KBService { }); if (lastAnswer && lastQuestionId) { - await lastAnswer.updateAttributes({ nextId: lastQuestionId }); + await lastAnswer.update({ nextId: lastQuestionId }); } lastAnswer = answer1; lastQuestionId = question1.questionId; @@ -449,7 +450,7 @@ export class KBService { where: { instanceId: instance.instanceId, packageId: packageId } }); - await deployer.rebuildIndex(instance); + await deployer.rebuildIndex(instance, new AzureDeployerService(deployer).getKBSearchSchema(instance.searchIndex)); } /** @@ -468,7 +469,7 @@ export class KBService { const p = await deployer.deployPackageToStorage(instance.instanceId, packageName); await this.importKbPackage(localPath, p, instance); - deployer.rebuildIndex(instance); + deployer.rebuildIndex(instance, new AzureDeployerService(deployer).getKBSearchSchema(instance.searchIndex)); logger.info(`[GBDeployer] Finished import of ${localPath}`); } } diff --git a/packages/security.gblib/index.ts b/packages/security.gblib/index.ts index 90ba69ac..0ae9674a 100644 --- a/packages/security.gblib/index.ts +++ b/packages/security.gblib/index.ts @@ -44,29 +44,17 @@ import { Sequelize } from 'sequelize-typescript'; import { GuaribasGroup, GuaribasUser, GuaribasUserGroup } from './models'; export class GBSecurityPackage implements IGBPackage { - public sysPackages: IGBPackage[] = null; + public sysPackages: IGBPackage[] = undefined; + public getDialogs(min: GBMinInstance) {} + public loadPackage(core: IGBCoreService, sequelize: Sequelize): void { - core.sequelize.addModels([ - GuaribasGroup, - GuaribasUser, - GuaribasUserGroup - ]); - - core; + core.sequelize.addModels([GuaribasGroup, GuaribasUser, GuaribasUserGroup]); } - public unloadPackage(core: IGBCoreService): void { + public unloadPackage(core: IGBCoreService): void {} - } + public loadBot(min: GBMinInstance): void {} - public loadBot(min: GBMinInstance): void { - - } - - public unloadBot(min: GBMinInstance): void { - - } - public onNewSession(min: GBMinInstance, step: any): void { - - } + public unloadBot(min: GBMinInstance): void {} + public onNewSession(min: GBMinInstance, step: any): void {} } diff --git a/packages/security.gblib/services/SecService.ts b/packages/security.gblib/services/SecService.ts index 277b0081..8ced609b 100644 --- a/packages/security.gblib/services/SecService.ts +++ b/packages/security.gblib/services/SecService.ts @@ -1,52 +1,12 @@ -/*****************************************************************************\ -| ( )_ _ | -| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ | -| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ | -| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) | -| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' | -| | | ( )_) | | -| (_) \___/' | -| | -| General Bots Copyright (c) Pragmatismo.io. All rights reserved. | -| Licensed under the AGPL-3.0. | -| | -| According to our dual licensing model, this program can be used either | -| under the terms of the GNU Affero General Public License, version 3, | -| or under a proprietary license. | -| | -| The texts of the GNU Affero General Public License with an additional | -| permission and of our proprietary license can be found at and | -| in the LICENSE file you have received along with this program. | -| | -| This program is distributed in the hope that it will be useful, | -| but WITHOUT ANY WARRANTY, without even the implied warranty of | -| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | -| GNU Affero General Public License for more details. | -| | -| "General Bots" is a registered trademark of Pragmatismo.io. | -| The licensing of the program under the AGPLv3 does not imply a | -| trademark license. Therefore any rights, title and interest in | -| our trademarks remain entirely with us. | -| | -\*****************************************************************************/ - -const Path = require('path'); const Fs = require('fs'); -const _ = require('lodash'); -const Parse = require('csv-parse'); -const Async = require('async'); const UrlJoin = require('url-join'); -const logger = require('../../../src/logger'); -import { GBService, GBServiceCallback, IGBInstance } from 'botlib'; +import { GBService, IGBInstance } from 'botlib'; import { GuaribasGroup, GuaribasUser, GuaribasUserGroup } from '../models'; export class SecService extends GBService { - public async importSecurityFile(localPath: string, instance: IGBInstance) { - const security = JSON.parse( - Fs.readFileSync(UrlJoin(localPath, 'security.json'), 'utf8') - ); + const security = JSON.parse(Fs.readFileSync(UrlJoin(localPath, 'security.json'), 'utf8')); security.groups.forEach(group => { const groupDb = GuaribasGroup.build({ instanceId: instance.instanceId, @@ -78,16 +38,15 @@ export class SecService extends GBService { channelName: string, displayName: string ): Promise { - return new Promise( - (resolve, reject) => { - - GuaribasUser.findOne({ - attributes: ['instanceId', 'internalAddress'], - where: { - instanceId: instanceId, - userSystemId: userSystemId - } - }).then(user => { + return new Promise((resolve, reject) => { + GuaribasUser.findOne({ + attributes: ['instanceId', 'internalAddress'], + where: { + instanceId: instanceId, + userSystemId: userSystemId + } + }) + .then(user => { if (!user) { user = GuaribasUser.build(); } @@ -99,7 +58,8 @@ export class SecService extends GBService { user.defaultChannel = channelName; user.save(); resolve(user); - }).error(reject); - }); + }) + .error(reject); + }); } } diff --git a/packages/whatsapp.gblib/index.ts b/packages/whatsapp.gblib/index.ts index 5bc37b89..ea79e0d1 100644 --- a/packages/whatsapp.gblib/index.ts +++ b/packages/whatsapp.gblib/index.ts @@ -44,31 +44,30 @@ import { Sequelize } from 'sequelize-typescript'; import { WhatsappDirectLine } from './services/WhatsappDirectLine'; export class GBWhatsappPackage implements IGBPackage { + public sysPackages: IGBPackage[] = undefined; + public getDialogs(min: GBMinInstance) {} - public sysPackages: IGBPackage[] = null; - public channel: WhatsappDirectLine; + public channel: WhatsappDirectLine; - public loadPackage(core: IGBCoreService, sequelize: Sequelize): void { + public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {} + + public unloadPackage(core: IGBCoreService): void {} + + public loadBot(min: GBMinInstance): void { + // Only loads engine if it is defined on services.json. + + if (min.instance.whatsappBotKey) { + this.channel = new WhatsappDirectLine( + min.botId, + min.instance.whatsappBotKey, + min.instance.whatsappServiceKey, + min.instance.whatsappServiceNumber, + min.instance.whatsappServiceUrl, + min.instance.whatsappServiceWebhookUrl + ); } + } - public unloadPackage(core: IGBCoreService): void { - - } - - public loadBot(min: GBMinInstance): void { - - // Only loads engine if it is defined on services.json. - - if (min.instance.whatsappBotKey) { - this.channel = new WhatsappDirectLine(min.botId, min.instance.whatsappBotKey, min.instance.whatsappServiceKey, - min.instance.whatsappServiceNumber, min.instance.whatsappServiceUrl, min.instance.whatsappServiceWebhookUrl); - } - } - - public unloadBot(min: GBMinInstance): void { - - } - public onNewSession(min: GBMinInstance, step: any): void { - - } + public unloadBot(min: GBMinInstance): void {} + public onNewSession(min: GBMinInstance, step: any): void {} } diff --git a/packages/whatsapp.gblib/services/WhatsappDirectLine.ts b/packages/whatsapp.gblib/services/WhatsappDirectLine.ts index 5203524e..0344b352 100644 --- a/packages/whatsapp.gblib/services/WhatsappDirectLine.ts +++ b/packages/whatsapp.gblib/services/WhatsappDirectLine.ts @@ -1,266 +1,213 @@ -/*****************************************************************************\ -| ( )_ _ | -| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ | -| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ | -| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) | -| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' | -| | | ( )_) | | -| (_) \___/' | -| | -| General Bots Copyright (c) Pragmatismo.io. All rights reserved. | -| Licensed under the AGPL-3.0. | -| | -| According to our dual licensing model, this program can be used either | -| under the terms of the GNU Affero General Public License, version 3, | -| or under a proprietary license. | -| | -| The texts of the GNU Affero General Public License with an additional | -| permission and of our proprietary license can be found at and | -| in the LICENSE file you have received along with this program. | -| | -| This program is distributed in the hope that it will be useful, | -| but WITHOUT ANY WARRANTY, without even the implied warranty of | -| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | -| GNU Affero General Public License for more details. | -| | -| "General Bots" is a registered trademark of Pragmatismo.io. | -| The licensing of the program under the AGPLv3 does not imply a | -| trademark license. Therefore any rights, title and interest in | -| our trademarks remain entirely with us. | -| | -\*****************************************************************************/ - -const Path = require('path'); -const Fs = require('fs'); -const _ = require('lodash'); -const Parse = require('csv-parse'); -const Async = require('async'); const UrlJoin = require('url-join'); const logger = require('../../../src/logger'); const Swagger = require('swagger-client'); const rp = require('request-promise'); +import { GBService } from 'botlib'; import * as request from 'request-promise-native'; -import { GBService, GBServiceCallback, IGBInstance } from 'botlib'; - export class WhatsappDirectLine extends GBService { + public pollInterval = 1000; + public directLineClientName = 'DirectLineClient'; + public directLineSpecUrl = 'https://docs.botframework.com/en-us/restapi/directline3/swagger.json'; - public pollInterval = 1000; - public directLineClientName = 'DirectLineClient'; - public directLineSpecUrl = 'https://docs.botframework.com/en-us/restapi/directline3/swagger.json'; + public directLineClient: any; + public whatsappServiceKey: string; + public whatsappServiceNumber: string; + public whatsappServiceUrl: string; + public whatsappServiceWebhookUrl: string; + public botId: string; + public watermark: string = null; - public directLineClient: any; - public whatsappServiceKey: string; - public whatsappServiceNumber: string; - public whatsappServiceUrl: string; - public whatsappServiceWebhookUrl: string; - public botId: string; - public watermark: string = null; + public conversationIds = {}; - public conversationIds = {}; + constructor( + botId, + directLineSecret, + whatsappServiceKey, + whatsappServiceNumber, + whatsappServiceUrl, + whatsappServiceWebhookUrl + ) { + super(); - constructor(botId, directLineSecret, whatsappServiceKey, whatsappServiceNumber, whatsappServiceUrl, whatsappServiceWebhookUrl) { + this.botId = botId; + this.whatsappServiceKey = whatsappServiceKey; + this.whatsappServiceNumber = whatsappServiceNumber; + this.whatsappServiceUrl = whatsappServiceUrl; + this.whatsappServiceWebhookUrl = whatsappServiceWebhookUrl; - super(); - - this.botId = botId; - this.whatsappServiceKey = whatsappServiceKey; - this.whatsappServiceNumber = whatsappServiceNumber; - this.whatsappServiceUrl = whatsappServiceUrl; - this.whatsappServiceWebhookUrl = whatsappServiceWebhookUrl; - - // TODO: Migrate to Swagger 3. - this.directLineClient = rp(this.directLineSpecUrl) - .then((spec) => { - return new Swagger({ - spec: JSON.parse(spec.trim()), - usePromise: true - }); - }) - .then(async (client) => { - client.clientAuthorizations.add('AuthorizationBotConnector', - new Swagger.ApiKeyAuthorization('Authorization', 'Bearer ' + - directLineSecret, 'header')); - - const options = { - method: 'POST', - url: UrlJoin(this.whatsappServiceUrl, 'webhook'), - qs: - { - token: this.whatsappServiceKey, - webhookUrl: `${this.whatsappServiceWebhookUrl}/instances/${this.botId}/whatsapp`, - set: true - }, - headers: - { - 'cache-control': 'no-cache' - } - }; - - try { - const result = await request.post(options); - logger.info(result); - } catch (error) { - logger.error('Error initializing 3rd party Whatsapp provider.', error); - } - - return client; - }) - .catch((err) => { - logger.error('Error initializing DirectLine client', err); - }); - - } - - public received(req, res) { - const text = req.body.messages[0].body; - const from = req.body.messages[0].author.split('@')[0]; - const fromName = req.body.messages[0].senderName; - - if (req.body.messages[0].fromMe) { - return; // Exit here. - } - - logger.info(`GBWhatsapp: Hook called. from: ${from}(${fromName}), text: ${text})`); - - const conversationId = this.conversationIds[from]; - - this.directLineClient.then((client) => { - - if (this.conversationIds[from] == null) { - - logger.info(`GBWhatsapp: Starting new conversation on Bot.`); - client.Conversations.Conversations_StartConversation() - .then((response) => { - return response.obj.conversationId; - }) - .then((conversationId) => { - - this.conversationIds[from] = conversationId; - this.inputMessage(client, conversationId, text, - from, fromName); - - this.pollMessages(client, conversationId, from, fromName); - }) - .catch((err) => { - console.error('Error starting conversation', err); - }); - - } else { - this.inputMessage(client, conversationId, text, - from, fromName); - } - res.end(); + // TODO: Migrate to Swagger 3. + this.directLineClient = rp(this.directLineSpecUrl) + .then(spec => { + return new Swagger({ + spec: JSON.parse(spec.trim()), + usePromise: true }); - } + }) + .then(async client => { + client.clientAuthorizations.add( + 'AuthorizationBotConnector', + new Swagger.ApiKeyAuthorization('Authorization', 'Bearer ' + directLineSecret, 'header') + ); - public inputMessage(client, conversationId, text, from, fromName) { - - client.Conversations.Conversations_PostActivity( - { - conversationId: conversationId, - activity: { - textFormat: 'plain', - text: text, - type: 'message', - from: { - id: from, - name: fromName - }, - replyToId: from - } - }).catch((err) => { - logger.error(`GBWhatsapp: Error receiving message: ${err}.`); - }); - - } - - public pollMessages(client, conversationId, from, fromName) { - - logger.info(`GBWhatsapp: Starting polling message for conversationId: - ${conversationId}.`); - - setInterval(() => { - client.Conversations.Conversations_GetActivities({ - conversationId: - conversationId, watermark: this.watermark - }) - .then((response) => { - this.watermark = response.obj.watermark; - return response.obj.activities; - }) - .then((activities) => { - this.printMessages(activities, conversationId, from, fromName); - }); - }, this.pollInterval); - } - - public printMessages(activities, conversationId, from, fromName) { - - if (activities && activities.length) { - - // Ignore own messages. - - activities = activities.filter((m) => (m.from.id === 'GeneralBots') && m.type === 'message'); - - if (activities.length) { - - // Print other messages. - - activities.forEach(activity => { - this.printMessage(activity, conversationId, from, fromName); - }); - } - } - } - - public printMessage(activity, conversationId, from, fromName) { - - let output = ''; - - if (activity.text) { - logger.info(`GBWhatsapp: MSG: ${activity.text}`); - output = activity.text; - } - - if (activity.attachments) { - activity.attachments.forEach((attachment) => { - switch (attachment.contentType) { - case 'application/vnd.microsoft.card.hero': - output += `\n${this.renderHeroCard(attachment)}`; - break; - - case 'image/png': - logger.info('Opening the requested image ' + attachment.contentUrl); - output += `\n${attachment.contentUrl}`; - break; - } - }); - } - - this.sendToDevice(conversationId, from, fromName, output); - } - - public renderHeroCard(attachment) { - return `${attachment.content.title} - ${attachment.content.text}`; - } - - public async sendToDevice(conversationId, to, toName, msg) { const options = { - method: 'POST', - url: UrlJoin(this.whatsappServiceUrl, 'message'), - qs: - { - token: this.whatsappServiceKey, - phone: to, - body: msg - }, - headers: - { - 'cache-control': 'no-cache' - } + method: 'POST', + url: UrlJoin(this.whatsappServiceUrl, 'webhook'), + qs: { + token: this.whatsappServiceKey, + webhookUrl: `${this.whatsappServiceWebhookUrl}/instances/${this.botId}/whatsapp`, + set: true + }, + headers: { + 'cache-control': 'no-cache' + } }; - const result = await request.get(options); + try { + const result = await request.post(options); + logger.info(result); + } catch (error) { + logger.error('Error initializing 3rd party Whatsapp provider.', error); + } + + return client; + }) + .catch(err => { + logger.error('Error initializing DirectLine client', err); + }); + } + + public received(req, res) { + const text = req.body.messages[0].body; + const from = req.body.messages[0].author.split('@')[0]; + const fromName = req.body.messages[0].senderName; + + if (req.body.messages[0].fromMe) { + return; // Exit here. } + + logger.info(`GBWhatsapp: Hook called. from: ${from}(${fromName}), text: ${text})`); + + const conversationId = this.conversationIds[from]; + + this.directLineClient.then(client => { + if (this.conversationIds[from] == undefined) { + logger.info(`GBWhatsapp: Starting new conversation on Bot.`); + client.Conversations.Conversations_StartConversation() + .then(response => { + return response.obj.conversationId; + }) + .then(conversationId => { + this.conversationIds[from] = conversationId; + this.inputMessage(client, conversationId, text, from, fromName); + + this.pollMessages(client, conversationId, from, fromName); + }) + .catch(err => { + console.error('Error starting conversation', err); + }); + } else { + this.inputMessage(client, conversationId, text, from, fromName); + } + res.end(); + }); + } + + public inputMessage(client, conversationId, text, from, fromName) { + client.Conversations.Conversations_PostActivity({ + conversationId: conversationId, + activity: { + textFormat: 'plain', + text: text, + type: 'message', + from: { + id: from, + name: fromName + }, + replyToId: from + } + }).catch(err => { + logger.error(`GBWhatsapp: Error receiving message: ${err}.`); + }); + } + + public pollMessages(client, conversationId, from, fromName) { + logger.info(`GBWhatsapp: Starting polling message for conversationId: + ${conversationId}.`); + + setInterval(() => { + client.Conversations.Conversations_GetActivities({ + conversationId: conversationId, + watermark: this.watermark + }) + .then(response => { + this.watermark = response.obj.watermark; + return response.obj.activities; + }) + .then(activities => { + this.printMessages(activities, conversationId, from, fromName); + }); + }, this.pollInterval); + } + + public printMessages(activities, conversationId, from, fromName) { + if (activities && activities.length) { + // Ignore own messages. + + activities = activities.filter(m => m.from.id === 'GeneralBots' && m.type === 'message'); + + if (activities.length) { + // Print other messages. + + activities.forEach(activity => { + this.printMessage(activity, conversationId, from, fromName); + }); + } + } + } + + public printMessage(activity, conversationId, from, fromName) { + let output = ''; + + if (activity.text) { + logger.info(`GBWhatsapp: MSG: ${activity.text}`); + output = activity.text; + } + + if (activity.attachments) { + activity.attachments.forEach(attachment => { + switch (attachment.contentType) { + case 'application/vnd.microsoft.card.hero': + output += `\n${this.renderHeroCard(attachment)}`; + break; + + case 'image/png': + logger.info('Opening the requested image ' + attachment.contentUrl); + output += `\n${attachment.contentUrl}`; + break; + } + }); + } + + this.sendToDevice(from, output); + } + + public renderHeroCard(attachment) { + return `${attachment.content.title} - ${attachment.content.text}`; + } + + public async sendToDevice(to, msg) { + const options = { + method: 'POST', + url: UrlJoin(this.whatsappServiceUrl, 'message'), + qs: { + token: this.whatsappServiceKey, + phone: to, + body: msg + }, + headers: { + 'cache-control': 'no-cache' + } + }; + } } diff --git a/src/app.ts b/src/app.ts index 4fd8ec5d..7b1617c1 100644 --- a/src/app.ts +++ b/src/app.ts @@ -40,7 +40,7 @@ const logger = require('./logger'); const express = require('express'); const bodyParser = require('body-parser'); -import { IGBInstance, IGBPackage } from 'botlib'; +import { IGBInstance, IGBPackage, IGBCoreService } from 'botlib'; import { GBAdminService } from '../packages/admin.gbapp/services/GBAdminService'; import { AzureDeployerService } from '../packages/azuredeployer.gbapp/services/AzureDeployerService'; import { GuaribasInstance } from '../packages/core.gbapp/models/GBModel'; @@ -50,8 +50,6 @@ import { GBCoreService } from '../packages/core.gbapp/services/GBCoreService'; import { GBDeployer } from '../packages/core.gbapp/services/GBDeployer'; import { GBImporter } from '../packages/core.gbapp/services/GBImporterService'; import { GBMinService } from '../packages/core.gbapp/services/GBMinService'; -import { GBVMService } from '../packages/core.gbapp/services/GBVMService'; -import { load } from 'dotenv'; const appPackages = new Array(); @@ -90,7 +88,7 @@ export class GBServer { // Reads basic configuration, initialize minimal services. GBConfigService.init(); - const core = new GBCoreService(); + const core: IGBCoreService = new GBCoreService(); const importer: GBImporter = new GBImporter(core); const deployer: GBDeployer = new GBDeployer(core, importer); @@ -103,7 +101,7 @@ export class GBServer { logger.info(`Establishing a development local proxy (ngrok)...`); const proxyAddress: string = await core.ensureProxy(port); - // Creates a boot instance or load it frmo storage. + // Creates a boot instance or load it from storage. let bootInstance: IGBInstance = null; try { @@ -130,9 +128,9 @@ export class GBServer { 'boot.gbot', 'packages/boot.gbot' ); - const fullInstance = Object.assign(packageInstance, bootInstance); + const fullInstance = { ...packageInstance, ...bootInstance }; await core.saveInstance(fullInstance); - let instances: GuaribasInstance[] = await core.loadAllInstances(core, azureDeployer, proxyAddress); + let instances: IGBInstance[] = await core.loadAllInstances(core, azureDeployer, proxyAddress); instances = await core.ensureInstances(instances, bootInstance, core); if (!bootInstance) { bootInstance = instances[0]; @@ -145,14 +143,13 @@ export class GBServer { // Deployment of local applications for the first time. - deployer.installDefaultGBUI(); + deployer.runOnce(); logger.info(`The Bot Server is in RUNNING mode...`); // Opens Navigator. core.openBrowserInDevelopment(); - } catch (err) { logger.error(`STOP: ${err} ${err.stack ? err.stack : ''}`); process.exit(1); diff --git a/tsconfig.json b/tsconfig.json index 6bc1d011..44e70235 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,6 +12,7 @@ "resolveJsonModule": true, "outDir": "./dist", "paths": { + "*": ["types/*"], "botlib/*": ["node_modules/botlib/*"], "pragmatismo-io-framework/*": ["node_modules/pragmatismo-io-framework/*"] },