new(all): TRUE multicloud.

This commit is contained in:
Rodrigo Rodriguez 2024-08-19 23:03:58 -03:00
parent 3ebf79c7b5
commit b004f8b4b5
76 changed files with 219 additions and 121 deletions

View file

@ -313,7 +313,7 @@ export class AdminDialog extends IGBDialog {
} }
if (packageName.indexOf('.') !== -1) { if (packageName.indexOf('.') !== -1) {
cmd1 = `deployPackage ${process.env.STORAGE_SITE} /${process.env.STORAGE_LIBRARY}/${botId}.gbai/${packageName}`; cmd1 = `deployPackage ${process.env.STORAGE_SITE} /${GBConfigService.get('STORAGE_LIBRARY')}/${botId}.gbai/${packageName}`;
} else { } else {
cmd1 = `deployPackage ${packageName}`; cmd1 = `deployPackage ${packageName}`;
} }

View file

@ -187,8 +187,8 @@ export class GBAdminService implements IGBAdminService {
await deployer['cleanupPackage'](min.instance, packageName); await deployer['cleanupPackage'](min.instance, packageName);
} }
if (process.env.STORAGE_FILE) { if (GBConfigService.get('STORAGE_FILE')) {
const path = Path.join(process.env.STORAGE_LIBRARY, gbaiPath); const path = Path.join(GBConfigService.get('STORAGE_LIBRARY'), gbaiPath);
Fs.cpSync(path, localFolder, { errorOnExist: false, force: true, recursive: true}); Fs.cpSync(path, localFolder, { errorOnExist: false, force: true, recursive: true});
} else { } else {
await deployer['downloadFolder'](min, Path.join('work', `${gbai}`), Path.basename(localFolder)); await deployer['downloadFolder'](min, Path.join('work', `${gbai}`), Path.basename(localFolder));

View file

@ -36,11 +36,11 @@
import { BotAdapter } from 'botbuilder'; import { BotAdapter } from 'botbuilder';
import { WaterfallDialog } from 'botbuilder-dialogs'; import { WaterfallDialog } from 'botbuilder-dialogs';
import { GBLog, GBMinInstance, IGBDialog } from 'botlib'; import { GBMinInstance, IGBDialog } from 'botlib';
import { GBServer } from '../../../src/app.js'; import { GBServer } from '../../../src/app.js';
import { GBConversationalService } from '../services/GBConversationalService.js';
import { Messages } from '../strings.js'; import { Messages } from '../strings.js';
import { GBLogEx } from '../services/GBLogEx.js'; import { GBLogEx } from '../services/GBLogEx.js';
import { GBConfigService } from '../services/GBConfigService.js';
/** /**
* Dialog for Welcoming people. * Dialog for Welcoming people.
@ -65,7 +65,7 @@ export class WelcomeDialog extends IGBDialog {
async step => { async step => {
if ( if (
GBServer.globals.entryPointDialog !== null && GBServer.globals.entryPointDialog !== null &&
min.instance.botId === process.env.BOT_ID && min.instance.botId === GBConfigService.get('BOT_ID') &&
step.context.activity.channelId === 'webchat' step.context.activity.channelId === 'webchat'
) { ) {
return step.replaceDialog(GBServer.globals.entryPointDialog); return step.replaceDialog(GBServer.globals.entryPointDialog);

View file

@ -42,7 +42,7 @@ import * as en from 'dotenv-extended';
*/ */
export class GBConfigService { export class GBConfigService {
public static getBoolean(value: string): boolean { public static getBoolean(value: string): boolean {
return (this.get(value) as unknown) as boolean; return this.get(value) as unknown as boolean;
} }
public static getServerPort(): string { public static getServerPort(): string {
if (process.env.PORT) { if (process.env.PORT) {
@ -84,8 +84,11 @@ export class GBConfigService {
case 'CLOUD_USERNAME': case 'CLOUD_USERNAME':
value = undefined; value = undefined;
break; break;
case 'STORAGE_LIBRARY':
value = `${process.env.HOME}/gbpackages`;
break;
case 'BOT_ID': case 'BOT_ID':
value = undefined; value = 'default';
break; break;
case 'CLOUD_PASSWORD': case 'CLOUD_PASSWORD':
value = undefined; value = undefined;
@ -103,7 +106,7 @@ export class GBConfigService {
value = undefined; value = undefined;
break; break;
case 'STORAGE_DIALECT': case 'STORAGE_DIALECT':
value = undefined; value = 'sqlite';
break; break;
case 'STORAGE_FILE': case 'STORAGE_FILE':
value = './data.db'; value = './data.db';
@ -160,7 +163,7 @@ export class GBConfigService {
value = true; value = true;
break; break;
case 'BOT_URL': case 'BOT_URL':
value = undefined; value = 'http://localhost:4242';
break; break;
case 'STORAGE_SERVER': case 'STORAGE_SERVER':
value = undefined; value = undefined;

View file

@ -50,6 +50,7 @@ import { GBSecurityPackage } from '../../security.gbapp/index.js';
import { GBWhatsappPackage } from '../../whatsapp.gblib/index.js'; import { GBWhatsappPackage } from '../../whatsapp.gblib/index.js';
import { GuaribasApplications, GuaribasInstance, GuaribasLog } from '../models/GBModel.js'; import { GuaribasApplications, GuaribasInstance, GuaribasLog } from '../models/GBModel.js';
import { GBConfigService } from './GBConfigService.js'; import { GBConfigService } from './GBConfigService.js';
import mkdirp from 'mkdirp';
import { GBAzureDeployerPackage } from '../../azuredeployer.gbapp/index.js'; import { GBAzureDeployerPackage } from '../../azuredeployer.gbapp/index.js';
import { GBSharePointPackage } from '../../sharepoint.gblib/index.js'; import { GBSharePointPackage } from '../../sharepoint.gblib/index.js';
import { CollectionUtil } from 'pragmatismo-io-framework'; import { CollectionUtil } from 'pragmatismo-io-framework';
@ -109,7 +110,7 @@ export class GBCoreService implements IGBCoreService {
constructor() { constructor() {
this.adminService = new GBAdminService(this); this.adminService = new GBAdminService(this);
} }
public async ensureInstances(instances: IGBInstance[], bootInstance: any, core: IGBCoreService) { } public async ensureInstances(instances: IGBInstance[], bootInstance: any, core: IGBCoreService) {}
/** /**
* Gets database config and connect to storage. Currently two databases * Gets database config and connect to storage. Currently two databases
@ -138,8 +139,8 @@ export class GBCoreService implements IGBCoreService {
const logging: boolean | Function = const logging: boolean | Function =
GBConfigService.get('STORAGE_LOGGING') === 'true' GBConfigService.get('STORAGE_LOGGING') === 'true'
? (str: string): void => { ? (str: string): void => {
GBLogEx.info(0, str); GBLogEx.info(0, str);
} }
: false; : false;
const encrypt: boolean = GBConfigService.get('STORAGE_ENCRYPT') === 'true'; const encrypt: boolean = GBConfigService.get('STORAGE_ENCRYPT') === 'true';
@ -231,7 +232,6 @@ export class GBCoreService implements IGBCoreService {
return out; return out;
} }
/** /**
* Loads all items to start several listeners. * Loads all items to start several listeners.
*/ */
@ -426,12 +426,11 @@ ENDPOINT_UPDATE=true
let instances: IGBInstance[]; let instances: IGBInstance[];
try { try {
instances = await core.loadInstances(); instances = await core.loadInstances();
const group = GBConfigService.get('CLOUD_GROUP')??GBConfigService.get('BOT_ID'); const group = GBConfigService.get('CLOUD_GROUP') ?? GBConfigService.get('BOT_ID');
if (process.env.ENDPOINT_UPDATE === 'true') { if (process.env.ENDPOINT_UPDATE === 'true') {
await CollectionUtil.asyncForEach(instances, async instance => { await CollectionUtil.asyncForEach(instances, async instance => {
GBLogEx.info(instance.instanceId, `Updating bot endpoint for ${instance.botId}...`); GBLogEx.info(instance.instanceId, `Updating bot endpoint for ${instance.botId}...`);
try { try {
await installationDeployer.updateBotProxy( await installationDeployer.updateBotProxy(
instance.botId, instance.botId,
group, group,
@ -459,7 +458,10 @@ ENDPOINT_UPDATE=true
Try setting STORAGE_SYNC to true in .env file. Error: ${error.message}.` Try setting STORAGE_SYNC to true in .env file. Error: ${error.message}.`
); );
} else { } else {
GBLogEx.info(0, `Storage is empty. After collecting storage structure from all .gbapps it will get synced.`); GBLogEx.info(
0,
`Storage is empty. After collecting storage structure from all .gbapps it will get synced.`
);
} }
} else { } else {
throw new Error(`Cannot connect to operating storage: ${error.message}.`); throw new Error(`Cannot connect to operating storage: ${error.message}.`);
@ -520,7 +522,6 @@ ENDPOINT_UPDATE=true
} }
} }
public async createBootInstance( public async createBootInstance(
core: GBCoreService, core: GBCoreService,
installationDeployer: IGBInstallationDeployer, installationDeployer: IGBInstallationDeployer,
@ -529,9 +530,10 @@ ENDPOINT_UPDATE=true
return await this.createBootInstanceEx( return await this.createBootInstanceEx(
core, core,
installationDeployer, installationDeployer,
proxyAddress, null, proxyAddress,
GBConfigService.get('FREE_TIER')); null,
GBConfigService.get('FREE_TIER')
);
} }
/** /**
* Creates the first bot instance (boot instance) used to "boot" the server. * Creates the first bot instance (boot instance) used to "boot" the server.
@ -548,8 +550,10 @@ ENDPOINT_UPDATE=true
) { ) {
GBLogEx.info(0, `Deploying cognitive infrastructure (on the cloud / on premises)...`); GBLogEx.info(0, `Deploying cognitive infrastructure (on the cloud / on premises)...`);
try { try {
const { instance, credentials, subscriptionId, installationDeployer } const { instance, credentials, subscriptionId, installationDeployer } = await StartDialog.createBaseInstance(
= await StartDialog.createBaseInstance(deployer, freeTier); deployer,
freeTier
);
installationDeployer['core'] = this; installationDeployer['core'] = this;
const changedInstance = await installationDeployer['deployFarm2']( const changedInstance = await installationDeployer['deployFarm2'](
proxyAddress, proxyAddress,
@ -668,27 +672,26 @@ ENDPOINT_UPDATE=true
} }
public async setConfig(min, name: string, value: any): Promise<any> { public async setConfig(min, name: string, value: any): Promise<any> {
// Handles calls for BASIC persistence on sheet files. // Handles calls for BASIC persistence on sheet files.
GBLog.info( `Defining Config.xlsx variable ${name}= '${value}'...`); GBLog.info(`Defining Config.xlsx variable ${name}= '${value}'...`);
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min); let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
const maxLines = 512; const maxLines = 512;
const file = "Config.xlsx"; const file = 'Config.xlsx';
const path = DialogKeywords.getGBAIPath(min.botId, `gbot`);; const path = DialogKeywords.getGBAIPath(min.botId, `gbot`);
let document = await (new SystemKeywords()).internalGetDocument(client, baseUrl, path, file); let document = await new SystemKeywords().internalGetDocument(client, baseUrl, path, file);
// Creates workbook session that will be discarded. // Creates book session that will be discarded.
let sheets = await client let sheets = await client.api(`${baseUrl}/drive/items/${document.id}/workbook/worksheets`).get();
.api(`${baseUrl}/drive/items/${document.id}/workbook/worksheets`)
.get();
let results = await client let results = await client
.api(`${baseUrl}/drive/items/${document.id}/workbook/worksheets('${sheets.value[0].name}')/range(address='A1:A${maxLines}')`) .api(
`${baseUrl}/drive/items/${document.id}/workbook/worksheets('${sheets.value[0].name}')/range(address='A1:A${maxLines}')`
)
.get(); .get();
const rows = results.text; const rows = results.text;
@ -708,12 +711,12 @@ ENDPOINT_UPDATE=true
body.values[0][0] = value; body.values[0][0] = value;
await client await client
.api(`${baseUrl}/drive/items/${document.id}/workbook/worksheets('${sheets.value[0].name}')/range(address='${address}')`) .api(
`${baseUrl}/drive/items/${document.id}/workbook/worksheets('${sheets.value[0].name}')/range(address='${address}')`
)
.patch(body); .patch(body);
} }
/** /**
* Get a dynamic param from instance. Dynamic params are defined in Config.xlsx * Get a dynamic param from instance. Dynamic params are defined in Config.xlsx
* and loaded into the work folder from comida command. * and loaded into the work folder from comida command.
@ -729,8 +732,7 @@ ENDPOINT_UPDATE=true
// Gets .gbot Params from specified bot. // Gets .gbot Params from specified bot.
if (instance.params) { if (instance.params) {
params = typeof instance.params === 'object' ? instance.params : JSON.parse(instance.params);
params = typeof (instance.params) === 'object' ? instance.params : JSON.parse(instance.params);
params = GBUtil.caseInsensitive(params); params = GBUtil.caseInsensitive(params);
value = params ? params[name] : defaultValue; value = params ? params[name] : defaultValue;
} }
@ -740,7 +742,6 @@ ENDPOINT_UPDATE=true
params = GBUtil.caseInsensitive(instance['dataValues']); params = GBUtil.caseInsensitive(instance['dataValues']);
if (params && !value) { if (params && !value) {
// Retrieves the value from specified bot instance (no params collection). // Retrieves the value from specified bot instance (no params collection).
value = instance['dataValues'][name]; value = instance['dataValues'][name];
@ -749,30 +750,26 @@ ENDPOINT_UPDATE=true
const minBoot = GBServer.globals.minBoot as any; const minBoot = GBServer.globals.minBoot as any;
if ( if (minBoot.instance && !value && instance.botId != minBoot.instance.botId) {
minBoot.instance &&
!value && instance.botId != minBoot.instance.botId) {
instance = minBoot.instance; instance = minBoot.instance;
if(instance.params){ if (instance.params) {
params = typeof (instance.params) === 'object' ? instance.params : JSON.parse(instance.params); params = typeof instance.params === 'object' ? instance.params : JSON.parse(instance.params);
params = GBUtil.caseInsensitive(params); params = GBUtil.caseInsensitive(params);
value = params ? params[name] : defaultValue; value = params ? params[name] : defaultValue;
} }
// If still did not found in boot bot params, try instance fields. // If still did not found in boot bot params, try instance fields.
if (!value){ if (!value) {
value = instance['dataValues'][name]; value = instance['dataValues'][name];
} }
if (!value){ if (!value) {
value = instance[name]; value = instance[name];
} }
} }
} }
if (value === undefined) { if (value === undefined) {
value = null; value = null;
} }
@ -786,9 +783,9 @@ ENDPOINT_UPDATE=true
if (value && typeof defaultValue === 'number') { if (value && typeof defaultValue === 'number') {
return new Number(value ? value : defaultValue ? defaultValue : 0).valueOf(); return new Number(value ? value : defaultValue ? defaultValue : 0).valueOf();
} }
const ret = value ?? defaultValue; const ret = value ?? defaultValue;
return ret; return ret;
} }
/** /**
@ -798,7 +795,7 @@ ENDPOINT_UPDATE=true
let params = null; let params = null;
const list = []; const list = [];
if (instance.params) { if (instance.params) {
params = typeof (instance.params) === 'object' ? instance.params : JSON.parse(instance.params); params = typeof instance.params === 'object' ? instance.params : JSON.parse(instance.params);
} }
Object.keys(params).forEach(e => { Object.keys(params).forEach(e => {
@ -810,5 +807,83 @@ ENDPOINT_UPDATE=true
return list; return list;
} }
public async ensureFolders(instances, deployer: GBDeployer) {
let libraryPath = GBConfigService.get('STORAGE_LIBRARY');
if (!Fs.existsSync(libraryPath)) {
mkdirp.sync(libraryPath);
}
await this.syncBotStorage(instances, 'default', deployer, libraryPath);
const files = Fs.readdirSync(libraryPath);
await CollectionUtil.asyncForEach(files, async file => {
if (file.trim().toLowerCase() !== 'default.gbai'){
let botId = file.replace(/\.gbai/, '');
await this.syncBotStorage(instances, botId, deployer, libraryPath);
}
});
}
private async syncBotStorage(instances: any, botId: any, deployer: GBDeployer, libraryPath: string) {
let instance = instances.find(p => p.botId.toLowerCase().trim() === botId.toLowerCase().trim());
if (!instance) {
GBLog.info(`Importing package ${botId}...`);
// Creates a bot.
let mobile = null,
email = null;
instance = await deployer.deployBlankBot(botId, mobile, email);
const gbaiPath = Path.join(libraryPath, `${botId}.gbai`);
if (!Fs.existsSync(gbaiPath)) {
Fs.mkdirSync(gbaiPath, { recursive: true });
const base = Path.join(process.env.PWD, 'templates', 'default.gbai');
Fs.cpSync(Path.join(base, `default.gbkb`), gbaiPath, {
errorOnExist: false,
force: true,
recursive: true
});
Fs.cpSync(Path.join(base, `default.gbot`), gbaiPath, {
errorOnExist: false,
force: true,
recursive: true
});
Fs.cpSync(Path.join(base, `default.gbtheme`), gbaiPath, {
errorOnExist: false,
force: true,
recursive: true
});
Fs.cpSync(Path.join(base, `default.gbdata`), gbaiPath, {
errorOnExist: false,
force: true,
recursive: true
});
Fs.cpSync(Path.join(base, `default.gbdialog`), gbaiPath, {
errorOnExist: false,
force: true,
recursive: true
});
Fs.cpSync(Path.join(base, `default.gbdrive`), gbaiPath, {
errorOnExist: false,
force: true,
recursive: true
});
}
}
}
} }

View file

@ -120,7 +120,7 @@ export class GBDeployer implements IGBDeployer {
); );
const siteId = process.env.STORAGE_SITE_ID; const siteId = process.env.STORAGE_SITE_ID;
const libraryId = process.env.STORAGE_LIBRARY; const libraryId = GBConfigService.get('STORAGE_LIBRARY');
const client = MicrosoftGraph.Client.init({ const client = MicrosoftGraph.Client.init({
authProvider: done => { authProvider: done => {
@ -222,22 +222,24 @@ export class GBDeployer implements IGBDeployer {
const instance = await this.importer.createBotInstance(botId); const instance = await this.importer.createBotInstance(botId);
const bootInstance = GBServer.globals.bootInstance; const bootInstance = GBServer.globals.bootInstance;
// Gets the access token to perform service operations. if (!GBConfigService.get('STORAGE_FILE')) {
// Gets the access token to perform service operations.
const accessToken = await (GBServer.globals.minBoot.adminService as any)['acquireElevatedToken']( const accessToken = await (GBServer.globals.minBoot.adminService as any)['acquireElevatedToken'](
bootInstance.instanceId, bootInstance.instanceId,
true true
); );
// Creates the MSFT application that will be associated to the bot. // Creates the MSFT application that will be associated to the bot.
const service = await AzureDeployerService.createInstance(this); const service = await AzureDeployerService.createInstance(this);
const application = await service.createApplication(accessToken, botId); const application = await service.createApplication(accessToken, botId);
// Fills new instance base information and get App secret.
// Fills new instance base information and get App secret. instance.marketplaceId = (application as any).appId;
instance.marketplacePassword = await service.createApplicationSecret(accessToken, (application as any).id);
}
instance.marketplaceId = (application as any).appId;
instance.marketplacePassword = await service.createApplicationSecret(accessToken, (application as any).id);
instance.adminPass = GBAdminService.getRndPassword(); instance.adminPass = GBAdminService.getRndPassword();
instance.title = botId; instance.title = botId;
instance.activationCode = instance.botId.substring(0, 15); instance.activationCode = instance.botId.substring(0, 15);
@ -249,10 +251,12 @@ export class GBDeployer implements IGBDeployer {
// Saves bot information to the store. // Saves bot information to the store.
await this.core.saveInstance(instance); await this.core.saveInstance(instance);
if (!GBConfigService.get('STORAGE_FILE')) {
await this.deployBotOnAzure(instance, GBServer.globals.publicAddress);
}
// Creates remaining objects on the cloud and updates instance information. // Creates remaining objects on the cloud and updates instance information.
return await this.deployBotFull(instance, GBServer.globals.publicAddress); return instance;
} }
/** /**
@ -267,7 +271,7 @@ export class GBDeployer implements IGBDeployer {
/** /**
* Performs all tasks of deploying a new bot on the cloud. * Performs all tasks of deploying a new bot on the cloud.
*/ */
public async deployBotFull(instance: IGBInstance, publicAddress: string): Promise<IGBInstance> { public async deployBotOnAzure(instance: IGBInstance, publicAddress: string): Promise<IGBInstance> {
// Reads base configuration from environent file. // Reads base configuration from environent file.
const service = await AzureDeployerService.createInstance(this); const service = await AzureDeployerService.createInstance(this);
@ -411,7 +415,7 @@ export class GBDeployer implements IGBDeployer {
public async deployBotFromLocalPath(localPath: string, publicAddress: string): Promise<void> { public async deployBotFromLocalPath(localPath: string, publicAddress: string): Promise<void> {
const packageName = Path.basename(localPath); const packageName = Path.basename(localPath);
const instance = await this.importer.importIfNotExistsBotPackage(undefined, packageName, localPath); const instance = await this.importer.importIfNotExistsBotPackage(undefined, packageName, localPath);
await this.deployBotFull(instance, publicAddress); await this.deployBotOnAzure(instance, publicAddress);
} }
/** /**

View file

@ -42,10 +42,11 @@ import { FacebookAdapter } from 'botbuilder-adapter-facebook';
import mkdirp from 'mkdirp'; import mkdirp from 'mkdirp';
import Fs from 'fs'; import Fs from 'fs';
import arrayBufferToBuffer from 'arraybuffer-to-buffer'; import arrayBufferToBuffer from 'arraybuffer-to-buffer';
import { v2 as webdav } from 'webdav-server';
import { NlpManager } from 'node-nlp'; import { NlpManager } from 'node-nlp';
import Koa from 'koa'; import Koa from 'koa';
import { v2 as webdav } from 'webdav-server';
import { createRpcServer } from '@push-rpc/core'; import { createRpcServer } from '@push-rpc/core';
import { start as startRouter } from '../../../packages/core.gbapp/services/router/bridge.js';
import wash from 'washyourmouthoutwithsoap'; import wash from 'washyourmouthoutwithsoap';
import { import {
AutoSaveStateMiddleware, AutoSaveStateMiddleware,
@ -172,6 +173,9 @@ export class GBMinService {
await CollectionUtil.asyncForEach( await CollectionUtil.asyncForEach(
instances, instances,
(async instance => { (async instance => {
startRouter(GBServer.globals.server, instance.botId);
try { try {
GBLog.info(`Mounting ${instance.botId}...`); GBLog.info(`Mounting ${instance.botId}...`);
await this['mountBot'](instance); await this['mountBot'](instance);
@ -256,7 +260,7 @@ export class GBMinService {
// Serves individual URL for each bot conversational interface. // Serves individual URL for each bot conversational interface.
await this.deployer['deployPackage2'](min, user, 'packages/default.gbtheme'); await this.deployer['deployPackage2'](min, user, 'templates/default.gbai/default.gbtheme');
// Install per bot deployed packages. // Install per bot deployed packages.
@ -280,7 +284,7 @@ export class GBMinService {
const gbai = DialogKeywords.getGBAIPath(min.botId); const gbai = DialogKeywords.getGBAIPath(min.botId);
let dir = `work/${gbai}/cache`; let dir = `work/${gbai}/cache`;
const botId = gbai.replace(/\.[^/.]+$/, ''); const botId = gbai.replace(/\.[^/.]+$/, '');
if (!Fs.existsSync(dir)) { if (!Fs.existsSync(dir)) {
mkdirp.sync(dir); mkdirp.sync(dir);
} }
@ -317,12 +321,12 @@ export class GBMinService {
mkdirp.sync(dir); mkdirp.sync(dir);
} }
if (process.env.STORAGE_FILE) { if (GBConfigService.get('STORAGE_FILE')) {
dir = Path.join(process.env.STORAGE_LIBRARY, 'work', gbai); dir = Path.join(GBConfigService.get('STORAGE_LIBRARY'), 'work', gbai);
const server = new webdav.WebDAVServer(); const server = GBServer.globals.webDavServer;
server.setFileSystem(`/${botId}`, new webdav.PhysicalFileSystem(dir), success => { server.setFileSystem(`/${botId}`, new webdav.PhysicalFileSystem(dir), success => {
server.start(() => console.log('WEBDAV READY')); GBLogEx.info(1, `WebDav for ${botId} loaded.`);
}); });
} }
// Loads Named Entity data for this bot. // Loads Named Entity data for this bot.
@ -691,8 +695,8 @@ export class GBMinService {
color2: this.core.getParam(instance, 'Color2', null) color2: this.core.getParam(instance, 'Color2', null)
}; };
if (process.env.STORAGE_FILE) { if (GBConfigService.get('STORAGE_FILE')) {
config['domain'] = `http://localhost:${process.env.PORT}/directline`; config['domain'] = `http://localhost:${process.env.PORT}/directline/${botId}`;
} else { } else {
const webchatTokenContainer = await this.getWebchatToken(instance); const webchatTokenContainer = await this.getWebchatToken(instance);
config['conversationId']= webchatTokenContainer.conversationId, config['conversationId']= webchatTokenContainer.conversationId,
@ -763,7 +767,7 @@ export class GBMinService {
? instance.marketplacePassword ? instance.marketplacePassword
: GBConfigService.get('MARKETPLACE_SECRET') : GBConfigService.get('MARKETPLACE_SECRET')
}; };
if (process.env.STORAGE_FILE) { if (GBConfigService.get('STORAGE_FILE')) {
config['clientOptions'] = { baseUri: `http://localhost:${process.env.PORT}` }; config['clientOptions'] = { baseUri: `http://localhost:${process.env.PORT}` };
} }
@ -830,8 +834,6 @@ export class GBMinService {
let url = `/api/messages/${instance.botId}`; let url = `/api/messages/${instance.botId}`;
GBServer.globals.server.post(url, receiver); GBServer.globals.server.post(url, receiver);
url = `/api/messages`;
GBServer.globals.server.post(url, receiver);
// NLP Manager. // NLP Manager.
@ -843,6 +845,10 @@ export class GBMinService {
GBServer.globals.minBoot.instance.marketplaceId = GBConfigService.get('MARKETPLACE_ID'); GBServer.globals.minBoot.instance.marketplaceId = GBConfigService.get('MARKETPLACE_ID');
GBServer.globals.minBoot.instance.marketplacePassword = GBConfigService.get('MARKETPLACE_SECRET'); GBServer.globals.minBoot.instance.marketplacePassword = GBConfigService.get('MARKETPLACE_SECRET');
} }
else{
url = `/api/messages`;
GBServer.globals.server.post(url, receiver);
}
if (min.instance.facebookWorkplaceVerifyToken) { if (min.instance.facebookWorkplaceVerifyToken) {
min['fbAdapter'] = new FacebookAdapter({ min['fbAdapter'] = new FacebookAdapter({
@ -1192,7 +1198,7 @@ export class GBMinService {
}; };
try { try {
if (process.env.STORAGE_FILE) { if (GBConfigService.get('STORAGE_FILE')) {
const context = adapter['createContext'](req); const context = adapter['createContext'](req);
context['_activity'] = context.activity.body; context['_activity'] = context.activity.body;
await handler(context); await handler(context);

View file

@ -11,7 +11,7 @@ const conversationsCleanupInterval = 10000;
const conversations: { [key: string]: IConversation } = {}; const conversations: { [key: string]: IConversation } = {};
const botDataStore: { [key: string]: IBotData } = {}; const botDataStore: { [key: string]: IBotData } = {};
export const getRouter = (serviceUrl: string, botUrl: string, conversationInitRequired = true): express.Router => { export const getRouter = (serviceUrl: string, botUrl: string, conversationInitRequired = true, botId): express.Router => {
const router = express.Router(); const router = express.Router();
router.use(bodyParser.json()); // for parsing application/json router.use(bodyParser.json()); // for parsing application/json
@ -22,8 +22,9 @@ export const getRouter = (serviceUrl: string, botUrl: string, conversationInitRe
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization, x-ms-bot-agent'); res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization, x-ms-bot-agent');
next(); next();
}); });
// CLIENT ENDPOINT // CLIENT ENDPOINT
router.options('/directline', (req, res) => { router.options(`/directline/${botId}/`, (req, res) => {
res.status(200).end(); res.status(200).end();
}); });
@ -53,7 +54,7 @@ export const getRouter = (serviceUrl: string, botUrl: string, conversationInitRe
}; };
router.post('/v3/directline/conversations',reqs ); router.post('/v3/directline/conversations',reqs );
router.post('/directline/conversations',reqs ); router.post(`/directline/${botId}/conversations`,reqs );
// Reconnect API // Reconnect API
router.get('/v3/directline/conversations/:conversationId', (req, res) => { router.get('/v3/directline/conversations/:conversationId', (req, res) => {
@ -69,7 +70,7 @@ export const getRouter = (serviceUrl: string, botUrl: string, conversationInitRe
}); });
// Gets activities from store (local history array for now) // Gets activities from store (local history array for now)
router.get('/directline/conversations/:conversationId/activities', (req, res) => { router.get(`/directline/${botId}/conversations/:conversationId/activities`, (req, res) => {
const watermark = req.query.watermark && req.query.watermark !== 'null' ? Number(req.query.watermark) : 0; const watermark = req.query.watermark && req.query.watermark !== 'null' ? Number(req.query.watermark) : 0;
const conversation = getConversation(req.params.conversationId, conversationInitRequired); const conversation = getConversation(req.params.conversationId, conversationInitRequired);
@ -95,7 +96,7 @@ export const getRouter = (serviceUrl: string, botUrl: string, conversationInitRe
}); });
// Sends message to bot. Assumes message activities // Sends message to bot. Assumes message activities
router.post('/directline/conversations/:conversationId/activities', (req, res) => { router.post(`/directline/${botId}/conversations/:conversationId/activities`, (req, res) => {
const incomingActivity = req.body; const incomingActivity = req.body;
// Make copy of activity. Add required fields // Make copy of activity. Add required fields
const activity = createMessageActivity(incomingActivity, serviceUrl, req.params.conversationId); const activity = createMessageActivity(incomingActivity, serviceUrl, req.params.conversationId);
@ -209,11 +210,11 @@ export const getRouter = (serviceUrl: string, botUrl: string, conversationInitRe
* @param conversationInitRequired Requires that a conversation is initialized before it is accessed, returning a 400 * @param conversationInitRequired Requires that a conversation is initialized before it is accessed, returning a 400
* when not the case. If set to false, a new conversation reference is created on the fly. This is true by default. * when not the case. If set to false, a new conversation reference is created on the fly. This is true by default.
*/ */
export const initializeRoutes = (app: express.Express, port: number, botUrl: string, conversationInitRequired = true) => { export const initializeRoutes = (app: express.Express, port: number, botUrl: string, conversationInitRequired = true, botId) => {
conversationsCleanup(); conversationsCleanup();
const directLineEndpoint = `http://127.0.0.1:${port}`; const directLineEndpoint = `http://127.0.0.1:${port}`;
const router = getRouter(directLineEndpoint, botUrl, conversationInitRequired); const router = getRouter(directLineEndpoint, botUrl, conversationInitRequired, botId);
app.use(router); app.use(router);
console.log(`Routing messages to bot on ${botUrl}`); console.log(`Routing messages to bot on ${botUrl}`);
@ -272,9 +273,9 @@ const setPrivateConversationData = (req: express.Request, res: express.Response)
res.status(200).send(setBotData(req.params.channelId, req.params.conversationId, req.params.userId, req.body)); res.status(200).send(setBotData(req.params.channelId, req.params.conversationId, req.params.userId, req.body));
}; };
export const start = (server)=>{ export const start = (server, botId)=>{
initializeRoutes(server, Number(process.env.PORT), `http://127.0.0.1:${process.env.PORT}/api/messages`); initializeRoutes(server, Number(process.env.PORT), `http://127.0.0.1:${process.env.PORT}/api/messages/${botId}`, null, botId);
} }
const deleteStateForUser = (req: express.Request, res: express.Response) => { const deleteStateForUser = (req: express.Request, res: express.Response) => {

View file

@ -1370,7 +1370,7 @@ export class KBService implements IGBKBService {
await this.importKbPackage(min, localPath, p, instance); await this.importKbPackage(min, localPath, p, instance);
GBDeployer.mountGBKBAssets(packageName, min.botId, localPath); GBDeployer.mountGBKBAssets(packageName, min.botId, localPath);
if (!process.env.STORAGE_FILE) { if (!GBConfigService.get('STORAGE_FILE')) {
const service = await AzureDeployerService.createInstance(deployer); const service = await AzureDeployerService.createInstance(deployer);
const searchIndex = instance.searchIndex ? instance.searchIndex : GBServer.globals.minBoot.instance.searchIndex; const searchIndex = instance.searchIndex ? instance.searchIndex : GBServer.globals.minBoot.instance.searchIndex;
await deployer.rebuildIndex(instance, service.getKBSearchSchema(searchIndex)); await deployer.rebuildIndex(instance, service.getKBSearchSchema(searchIndex));

View file

@ -65,4 +65,5 @@ export class RootData {
public dbg; public dbg;
public img; public img;
indexSemaphore: any; indexSemaphore: any;
public webDavServer;
} }

View file

@ -40,8 +40,7 @@ import bodyParser from 'body-parser';
import { GBLog, GBMinInstance, IGBCoreService, IGBInstance } from 'botlib'; import { GBLog, GBMinInstance, IGBCoreService, IGBInstance } from 'botlib';
import child_process from 'child_process'; import child_process from 'child_process';
import express from 'express'; import express from 'express';
import {start as startRouter} from '../packages/core.gbapp/services/router/bridge.js' import { v2 as webdav } from 'webdav-server';
import fs from 'fs'; import fs from 'fs';
import http from 'http'; import http from 'http';
import httpProxy from 'http-proxy'; import httpProxy from 'http-proxy';
@ -89,7 +88,7 @@ export class GBServer {
const server = express(); const server = express();
this.initEndpointsDocs(server); this.initEndpointsDocs(server);
startRouter(server);
GBServer.globals.server = server; GBServer.globals.server = server;
GBServer.globals.httpsServer = null; GBServer.globals.httpsServer = null;
@ -105,6 +104,8 @@ export class GBServer {
GBServer.globals.debuggers = []; GBServer.globals.debuggers = [];
GBServer.globals.users = []; GBServer.globals.users = [];
GBServer.globals.indexSemaphore = new Mutex(); GBServer.globals.indexSemaphore = new Mutex();
GBServer.globals.webDavServer = new webdav.WebDAVServer();
GBServer.globals.webDavServer.start();
server.use(bodyParser.json()); server.use(bodyParser.json());
server.use(bodyParser.json({ limit: '1mb' })); server.use(bodyParser.json({ limit: '1mb' }));
@ -121,7 +122,10 @@ export class GBServer {
}); });
process.on('uncaughtException', (err, p) => { process.on('uncaughtException', (err, p) => {
GBLogEx.error(0, `GBEXCEPTION: ${GBUtil.toYAML(JSON.parse(JSON.stringify(err, Object.getOwnPropertyNames(err))))}`); GBLogEx.error(
0,
`GBEXCEPTION: ${GBUtil.toYAML(JSON.parse(JSON.stringify(err, Object.getOwnPropertyNames(err))))}`
);
}); });
process.on('unhandledRejection', (err, p) => { process.on('unhandledRejection', (err, p) => {
@ -134,7 +138,10 @@ export class GBServer {
} }
if (!bypass) { if (!bypass) {
GBLogEx.error(0,`GBREJECTION: ${GBUtil.toYAML(JSON.parse(JSON.stringify(err, Object.getOwnPropertyNames(err))))}`); GBLogEx.error(
0,
`GBREJECTION: ${GBUtil.toYAML(JSON.parse(JSON.stringify(err, Object.getOwnPropertyNames(err))))}`
);
} }
}); });
@ -150,7 +157,7 @@ export class GBServer {
(async () => { (async () => {
try { try {
GBLogEx.info(0, `Now accepting connections on ${port}...`); GBLogEx.info(0, `Now accepting connections on ${port}...`);
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'; process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';
// Reads basic configuration, initialize minimal services. // Reads basic configuration, initialize minimal services.
@ -186,7 +193,6 @@ export class GBServer {
} else if (GBConfigService.get('STORAGE_FILE')) { } else if (GBConfigService.get('STORAGE_FILE')) {
await core.initStorage(); await core.initStorage();
} else { } else {
runOnce = true;
[GBServer.globals.bootInstance, azureDeployer] = await core['createBootInstanceEx']( [GBServer.globals.bootInstance, azureDeployer] = await core['createBootInstanceEx'](
core, core,
null, null,
@ -194,6 +200,7 @@ export class GBServer {
deployer, deployer,
GBConfigService.get('FREE_TIER') GBConfigService.get('FREE_TIER')
); );
await core.saveInstance(GBServer.globals.bootInstance);
} }
core.ensureAdminIsSecured(); core.ensureAdminIsSecured();
@ -207,10 +214,6 @@ export class GBServer {
await deployer.deployPackages(core, server, GBServer.globals.appPackages); await deployer.deployPackages(core, server, GBServer.globals.appPackages);
await core.syncDatabaseStructure(); await core.syncDatabaseStructure();
if (runOnce) {
await core.saveInstance(GBServer.globals.bootInstance);
}
// Deployment of local applications for the first time. // Deployment of local applications for the first time.
if (GBConfigService.get('DISABLE_WEB') !== 'true') { if (GBConfigService.get('DISABLE_WEB') !== 'true') {
@ -224,23 +227,27 @@ export class GBServer {
GBServer.globals.publicAddress GBServer.globals.publicAddress
); );
if (instances.length === 0) { if (instances. length === 0) {
const instance = await importer.importIfNotExistsBotPackage( if (!GBConfigService.get('STORAGE_FILE')) {
GBConfigService.get('BOT_ID'), const instance = await importer.importIfNotExistsBotPackage(
'boot.gbot', GBConfigService.get('BOT_ID'),
'packages/boot.gbot', 'boot.gbot',
GBServer.globals.bootInstance 'packages/boot.gbot',
); GBServer.globals.bootInstance
);
instances.push(instance); instances.push(instance);
GBServer.globals.minBoot.instance = instances[0]; GBServer.globals.minBoot.instance = instances[0];
GBServer.globals.bootInstance = instances[0]; GBServer.globals.bootInstance = instances[0];
await deployer.deployBotFull(instance, GBServer.globals.publicAddress); await deployer.deployBotOnAzure(instance, GBServer.globals.publicAddress);
// Runs the search even with empty content to create structure. // Runs the search even with empty content to create structure.
await azureDeployer['runSearch'](instance); await azureDeployer['runSearch'](instance);
}
} }
await core['ensureFolders'](instances, deployer);
GBServer.globals.bootInstance = instances[0]; GBServer.globals.bootInstance = instances[0];
@ -251,7 +258,7 @@ export class GBServer {
const minService: GBMinService = new GBMinService(core, conversationalService, adminService, deployer); const minService: GBMinService = new GBMinService(core, conversationalService, adminService, deployer);
GBServer.globals.minService = minService; GBServer.globals.minService = minService;
await minService.buildMin(instances); await minService.buildMin(instances);
server.all('*', async (req, res, next) => { server.all('*', async (req, res, next) => {
const host = req.headers.host; const host = req.headers.host;

View file

@ -36,6 +36,7 @@
import * as YAML from 'yaml'; import * as YAML from 'yaml';
import SwaggerClient from 'swagger-client'; import SwaggerClient from 'swagger-client';
import Fs from 'fs'; import Fs from 'fs';
import { GBConfigService } from '../packages/core.gbapp/services/GBConfigService.js';
export class GBUtil { export class GBUtil {
public static repeat(chr, count) { public static repeat(chr, count) {
@ -69,14 +70,14 @@ export class GBUtil {
public static async getDirectLineClient(min) { public static async getDirectLineClient(min) {
let config = { let config = {
url: `http://127.0.0.1:${process.env.port}/api/messages`, url: `http://127.0.0.1:${GBConfigService.get('PORT')}/api/messages`,
spec: JSON.parse(Fs.readFileSync('directline-3.0.json', 'utf8')), spec: JSON.parse(Fs.readFileSync('directline-3.0.json', 'utf8')),
requestInterceptor: req => { requestInterceptor: req => {
req.headers['Authorization'] = `Bearer ${min.instance.webchatKey}`; req.headers['Authorization'] = `Bearer ${min.instance.webchatKey}`;
} }
}; };
if (process.env.STORAGE_FILE) { if (GBConfigService.get('STORAGE_FILE')) {
config['spec'].servers = [{ url: `http://127.0.0.1:${process.env.PORT}/api/messages` }]; config['spec'].servers = [{ url: `http://127.0.0.1:${GBConfigService.get('PORT')}/api/messages` }];
config['openapi'] = '3.0.0'; config['openapi'] = '3.0.0';
} }
return await new SwaggerClient(config); return await new SwaggerClient(config);

View file

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View file

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View file

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

View file

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

View file

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View file

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View file

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2 KiB

View file

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View file

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View file

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View file

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View file

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View file

Before

Width:  |  Height:  |  Size: 958 B

After

Width:  |  Height:  |  Size: 958 B

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View file

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View file

Before

Width:  |  Height:  |  Size: 247 KiB

After

Width:  |  Height:  |  Size: 247 KiB

View file

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View file

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

View file

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB