feat(whatsapp.gblib): Same chat-api provider now shared between instances and deploy improvements.
This commit is contained in:
		
							parent
							
								
									9a961e72a1
								
							
						
					
					
						commit
						b2da413f0f
					
				
					 9 changed files with 54 additions and 47 deletions
				
			
		| 
						 | 
				
			
			@ -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",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<void> {
 | 
			
		||||
    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<void> {
 | 
			
		||||
    const packageFolder = Path.join(process.env.PWD, 'work', packageName);
 | 
			
		||||
    rimraf.sync(packageFolder);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async deployPackageToStorage(instanceId: number, packageName: string): Promise<GuaribasPackage> {
 | 
			
		||||
    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:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.`);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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: {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										17
									
								
								src/app.ts
									
										
									
									
									
								
							
							
						
						
									
										17
									
								
								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.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue