From b2da413f0f64076ade4c69bae62922a270731b3e Mon Sep 17 00:00:00 2001 From: Rodrigo Rodriguez Date: Thu, 22 Aug 2019 17:28:11 -0300 Subject: [PATCH] feat(whatsapp.gblib): Same chat-api provider now shared between instances and deploy improvements. --- package.json | 1 - .../services/AzureDeployerService.ts | 1 + packages/core.gbapp/services/GBDeployer.ts | 31 ++++++++++------- .../core.gbapp/services/GBImporterService.ts | 7 ++-- packages/core.gbapp/services/GBMinService.ts | 33 +++++++++++-------- packages/kb.gbapp/dialogs/AskDialog.ts | 3 -- packages/whatsapp.gblib/index.ts | 6 +--- .../services/WhatsappDirectLine.ts | 2 +- src/app.ts | 17 +++++----- 9 files changed, 54 insertions(+), 47 deletions(-) diff --git a/package.json b/package.json index 4f5728cb..7feb48b4 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,6 @@ "cli-spinner": "0.2.10", "csv-parse": "4.4.1", "dotenv-extended": "2.4.0", - "empty-dir": "^2.0.0", "express": "4.16.4", "express-promise-router": "3.0.3", "express-remove-route": "^1.0.0", diff --git a/packages/azuredeployer.gbapp/services/AzureDeployerService.ts b/packages/azuredeployer.gbapp/services/AzureDeployerService.ts index 8fb816f7..35b43932 100644 --- a/packages/azuredeployer.gbapp/services/AzureDeployerService.ts +++ b/packages/azuredeployer.gbapp/services/AzureDeployerService.ts @@ -537,6 +537,7 @@ export class AzureDeployerService implements IGBInstallationDeployer { const resChannel = await httpClient.sendRequest(req); const key = JSON.parse(resChannel.bodyAsText).properties.properties.sites[0].key; instance.webchatKey = key; + instance.whatsappBotKey = key; resolve(instance); } catch (error) { reject(error); diff --git a/packages/core.gbapp/services/GBDeployer.ts b/packages/core.gbapp/services/GBDeployer.ts index fe04dcdb..c568fac2 100644 --- a/packages/core.gbapp/services/GBDeployer.ts +++ b/packages/core.gbapp/services/GBDeployer.ts @@ -43,7 +43,7 @@ const WaitUntil = require('wait-until'); const express = require('express'); const child_process = require('child_process'); const graph = require('@microsoft/microsoft-graph-client'); -const emptyDir = require('empty-dir'); +const rimraf = require('rimraf'); import { GBError, GBLog, GBMinInstance, IGBCoreService, IGBInstance, IGBPackage } from 'botlib'; import { AzureSearch } from 'pragmatismo-io-framework'; @@ -190,6 +190,8 @@ export class GBDeployer { ); } else { + + console.log(GBServer.globals.bootInstance); instance = Object.assign(instance, GBServer.globals.bootInstance); instance = await service.internalDeployBot( instance, @@ -214,17 +216,13 @@ export class GBDeployer { /** - * Deploys a bot to the storage. + * UndDeploys a bot to the storage. */ public async undeployBot(botId: string, packageName: string): Promise { const service = new AzureDeployerService(this); - const username = GBConfigService.get('CLOUD_USERNAME'); - const password = GBConfigService.get('CLOUD_PASSWORD'); const group = GBConfigService.get('CLOUD_GROUP'); - const subscriptionId = GBConfigService.get('CLOUD_SUBSCRIPTIONID'); - const accessToken = await GBAdminService.getADALTokenFromUsername(username, password); if (await service.botExists(botId, group)) { @@ -235,10 +233,14 @@ export class GBDeployer { } GBServer.globals.minService.unmountBot(botId); await this.core.deleteInstance(botId); - const packageFolder = urlJoin(process.env.PWD, 'work', packageName); - await emptyDir(packageFolder); + const packageFolder =Path.join(process.env.PWD, 'work', packageName); + rimraf.sync(packageFolder); } + public async undeployTheme(packageName: string): Promise { + const packageFolder = Path.join(process.env.PWD, 'work', packageName); + rimraf.sync(packageFolder); + } public async deployPackageToStorage(instanceId: number, packageName: string): Promise { return GuaribasPackage.create({ @@ -270,14 +272,14 @@ export class GBDeployer { case '.gbkb': const service = new KBService(this.core.sequelize); - await service.deployKb(this.core, this, localPath); + break; case '.gbdialog': const vm = new GBVMService(); - await vm.loadDialogPackage(localPath, min, this.core, this); - + break; + default: const err = GBError.create(`Unhandled package type: ${packageType}.`); Promise.reject(err); @@ -300,13 +302,18 @@ export class GBDeployer { case '.gbkb': const service = new KBService(this.core.sequelize); - return service.undeployKbFromStorage(instance, this, p.packageId); case '.gbui': + + break; + + case '.gbtheme': + this.undeployTheme(packageName); break; case '.gbdialog': + break; default: diff --git a/packages/core.gbapp/services/GBImporterService.ts b/packages/core.gbapp/services/GBImporterService.ts index dd1079ea..e9fba61c 100644 --- a/packages/core.gbapp/services/GBImporterService.ts +++ b/packages/core.gbapp/services/GBImporterService.ts @@ -41,6 +41,7 @@ import fs = require('fs'); import urlJoin = require('url-join'); import { GuaribasInstance } from '../models/GBModel'; import { GBConfigService } from './GBConfigService'; +import { GBServer } from '../../../src/app'; /** * Handles the importing of packages. @@ -61,7 +62,7 @@ export class GBImporter { botId = GBConfigService.get('BOT_ID'); } const instance = await this.core.loadInstance(botId); - + return await this.createOrUpdateInstanceInternal(instance, botId, localPath, packageJson); } @@ -70,7 +71,7 @@ export class GBImporter { const settings = JSON.parse(fs.readFileSync(urlJoin(localPath, 'settings.json'), 'utf8')); const servicesJson = JSON.parse(fs.readFileSync(urlJoin(localPath, 'services.json'), 'utf8')); - packageJson = { ...packageJson, ...settings, ...servicesJson }; + packageJson = { ...GBServer.globals.bootInstance, ...packageJson, ...settings, ...servicesJson }; if (botId !== undefined) { packageJson.botId = botId; @@ -81,7 +82,7 @@ export class GBImporter { return this.core.saveInstance(instance); } else { - return GuaribasInstance.create(packageJson); + return await GuaribasInstance.create(packageJson); } } } diff --git a/packages/core.gbapp/services/GBMinService.ts b/packages/core.gbapp/services/GBMinService.ts index df9a3e39..88170b19 100644 --- a/packages/core.gbapp/services/GBMinService.ts +++ b/packages/core.gbapp/services/GBMinService.ts @@ -125,6 +125,16 @@ export class GBMinService { })(); }); } + const url = '/webhooks/whatsapp'; + GBServer.globals.server.post(url, async (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; + + let botId = 'subway-prd'; + const min = GBServer.globals.minInstances.filter(p => p.botId === botId)[0]; + (min as any).whatsAppDirectLine.received(req, res); + }); await Promise.all( instances.map(async instance => { @@ -137,30 +147,33 @@ export class GBMinService { public async unmountBot(botId: string) { const url = `/api/messages/${botId}`; - removeRoute(GBServer.globals.server,url); + removeRoute(GBServer.globals.server, url); const uiUrl = `/${botId}`; removeRoute(GBServer.globals.server, uiUrl); + + GBServer.globals.minInstances = GBServer.globals.minInstances.filter(p => p.botId !== botId); } public async mountBot(instance: IGBInstance) { - + // Build bot adapter. const { min, adapter, conversationState } = await this.buildBotAdapter(instance, GBServer.globals.publicAddress, GBServer.globals.sysPackages); - + GBServer.globals.minInstances.push(min); + // Install default VBA module. //this.deployer.deployPackage(min, 'packages/default.gbdialog'); - + // Call the loadBot context.activity for all packages. this.invokeLoadBot(GBServer.globals.appPackages, GBServer.globals.sysPackages, min, GBServer.globals.server); - + // Serves individual URL for each bot conversational interface... const url = `/api/messages/${instance.botId}`; GBServer.globals.server.post(url, async (req, res) => { await this.receiver(adapter, req, res, conversationState, min, instance, GBServer.globals.appPackages); }); GBLog.info(`GeneralBots(${instance.engineName}) listening on: ${url}.`); - + // Serves individual URL for each bot user interface. if (process.env.DISABLE_WEB !== 'true') { const uiUrl = `/${instance.botId}`; @@ -172,7 +185,7 @@ export class GBMinService { // There they will authenticate and give their consent to allow this app access to // some resource they own. this.handleOAuthRequests(GBServer.globals.server, min); - + // After consent is granted AAD redirects here. The ADAL library // is invoked via the AuthenticationContext and retrieves an // access token that can be used to access the user owned resource. @@ -359,12 +372,6 @@ export class GBMinService { let index = 0; sysPackages.forEach(e => { e.loadBot(min); - if (index === 6) { // TODO: Remove this magic number and use a map. - const url = '/instances/:botId/whatsapp'; - server.post(url, async (req, res) => { - await (e as any).channel.received(req, res); - }); - } index++; }, this); diff --git a/packages/kb.gbapp/dialogs/AskDialog.ts b/packages/kb.gbapp/dialogs/AskDialog.ts index 968165df..e940d97c 100644 --- a/packages/kb.gbapp/dialogs/AskDialog.ts +++ b/packages/kb.gbapp/dialogs/AskDialog.ts @@ -66,9 +66,6 @@ export class AskDialog extends IGBDialog { min.dialogs.add(new WaterfallDialog('/answerEvent', AskDialog.getAnswerEventDialog(service, min))); min.dialogs.add(new WaterfallDialog('/answer', AskDialog.getAnswerDialog(min, service))); min.dialogs.add(new WaterfallDialog('/ask', AskDialog.getAskDialog(min))); - - - } private static getAskDialog(min: GBMinInstance) { diff --git a/packages/whatsapp.gblib/index.ts b/packages/whatsapp.gblib/index.ts index c518ebd3..d5aab15e 100644 --- a/packages/whatsapp.gblib/index.ts +++ b/packages/whatsapp.gblib/index.ts @@ -46,13 +46,12 @@ import { WhatsappDirectLine } from './services/WhatsappDirectLine'; export class GBWhatsappPackage implements IGBPackage { public sysPackages: IGBPackage[]; - public channel: WhatsappDirectLine; public loadBot(min: GBMinInstance): void { // Only loads engine if it is defined on services.json. if (min.instance.whatsappBotKey !== undefined) { - this.channel = new WhatsappDirectLine( + min.whatsAppDirectLine = new WhatsappDirectLine( min.botId, min.instance.whatsappBotKey, min.instance.whatsappServiceKey, @@ -62,9 +61,6 @@ export class GBWhatsappPackage implements IGBPackage { } } - public getChannel() { - return this.channel; - } public getDialogs(min: GBMinInstance) { GBLog.verbose(`getDialogs called.`); diff --git a/packages/whatsapp.gblib/services/WhatsappDirectLine.ts b/packages/whatsapp.gblib/services/WhatsappDirectLine.ts index 38215209..8cb4f51a 100644 --- a/packages/whatsapp.gblib/services/WhatsappDirectLine.ts +++ b/packages/whatsapp.gblib/services/WhatsappDirectLine.ts @@ -86,7 +86,7 @@ export class WhatsappDirectLine extends GBService { url: urlJoin(this.whatsappServiceUrl, 'webhook'), qs: { token: this.whatsappServiceKey, - webhookUrl: `${GBServer.globals.publicAddress}/instances/${this.botId}/whatsapp`, + webhookUrl: `${GBServer.globals.publicAddress}/webhooks/whatsapp`, set: true }, headers: { diff --git a/src/app.ts b/src/app.ts index 1c53f204..97a1e401 100644 --- a/src/app.ts +++ b/src/app.ts @@ -49,8 +49,6 @@ import { GBDeployer } from '../packages/core.gbapp/services/GBDeployer'; import { GBImporter } from '../packages/core.gbapp/services/GBImporterService'; import { GBMinService } from '../packages/core.gbapp/services/GBMinService'; -const appPackages: IGBPackage[] = []; - /** * Global shared server data; */ @@ -61,6 +59,7 @@ export class RootData { public appPackages: any[]; minService: GBMinService; bootInstance: IGBInstance; + public minInstances: any[]; } /** @@ -81,6 +80,10 @@ export class GBServer { const port = GBConfigService.getServerPort(); const server = express(); GBServer.globals.server = server; + GBServer.globals.appPackages = []; + GBServer.globals.sysPackages = []; + GBServer.globals.minInstances = []; + server.use(bodyParser.json()); server.use(bodyParser.urlencoded({ extended: true })); @@ -92,7 +95,6 @@ export class GBServer { // Reads basic configuration, initialize minimal services. const core: IGBCoreService = new GBCoreService(); - const importer: GBImporter = new GBImporter(core); const deployer: GBDeployer = new GBDeployer(core, importer); const azureDeployer: AzureDeployerService = new AzureDeployerService(deployer); @@ -118,7 +120,6 @@ export class GBServer { // Creates a boot instance or load it from storage. - try { await core.initStorage(); } catch (error) { @@ -132,11 +133,9 @@ export class GBServer { // Deploys system and user packages. GBLog.info(`Deploying packages...`); - const sysPackages = core.loadSysPackages(core); + GBServer.globals.sysPackages = core.loadSysPackages(core); await core.checkStorage(azureDeployer); - await deployer.deployPackages(core, server, appPackages); - GBServer.globals.sysPackages = sysPackages; - GBServer.globals.appPackages = appPackages; + await deployer.deployPackages(core, server, GBServer.globals.appPackages); // Loads boot bot and other instances. @@ -163,7 +162,7 @@ export class GBServer { const minService: GBMinService = new GBMinService(core, conversationalService, adminService, deployer); GBServer.globals.minService = minService; - await minService.buildMin( instances); + await minService.buildMin(instances); // Deployment of local applications for the first time.