* FIX: Admin now is internationalized.

* FIX: Webchat now receives a private token.
* FIX: OAuth2 now has got revised and included state to avoid CSRF attacks.
* FIX: Now server will only start with a secure administration password.
This commit is contained in:
Rodrigo Rodriguez 2018-09-24 11:04:36 -03:00
parent 7375f179b2
commit 3832f27451
19 changed files with 678 additions and 461 deletions

View file

@ -187,7 +187,13 @@ here is a list of admin commands related to deploying .gb* files.
| deployPackage | Deploy a KB package. Usage **deployPackage** [package-name]. Then, you need to run rebuildIndex. |
| undeployPackage | Undeploy a KB. Usage **undeployPackage** [package-name]. |
| redeployPackage | Undeploy and then deploys the KB. Usage **redeployPackage** [package-name]. Then, you need to run rebuildIndex. |
| rebuildIndex | Rebuild Azure Search indexes, must be run after **deployPackage** or **redeployPackage**. |
| setupSecurity | Setup connection to user directories. |
Discontinued commands:
| Command | Description |Reason |
|-----------------| -----------------------------------------------------------------------------------------------------------------|------|
| rebuildIndex | Rebuild Azure Search indexes, must be run after **deployPackage** or **redeployPackage**. | Now it is called automatically |
### Credits & Inspiration

View file

@ -1,5 +1,12 @@
# Release History
## Version 0.1.3
* FIX: Admin now is internationalized.
* FIX: Webchat now receives a private token.
* FIX: OAuth2 now has got revised and included state to avoid CSRF attacks.
* FIX: Now server will only start with a secure administration password.
## Version 0.1.2
* NEW: kb.gbapp now has a complete browser of excel articles.

View file

@ -42,59 +42,32 @@ import { GBConfigService } from "../../core.gbapp/services/GBConfigService";
import { KBService } from "./../../kb.gbapp/services/KBService";
import { BotAdapter } from "botbuilder";
import { GBAdminService } from "../services/GBAdminService";
import { Messages } from "../strings";
/**
* Dialogs for administration tasks.
*/
export class AdminDialog extends IGBDialog {
static async undeployPackageCommand(text: any, min: GBMinInstance, dc) {
static async undeployPackageCommand(text: any, min: GBMinInstance) {
let packageName = text.split(" ")[1];
let importer = new GBImporter(min.core);
let deployer = new GBDeployer(min.core, importer);
dc.context.sendActivity(`Undeploying package ${packageName}...`);
await deployer.undeployPackageFromLocalPath(
min.instance,
UrlJoin("deploy", packageName)
);
dc.context.sendActivity(`Package ${packageName} undeployed...`);
}
static async deployPackageCommand(
text: string,
dc,
deployer: GBDeployer,
min: GBMinInstance
static async deployPackageCommand(text: string,
deployer: GBDeployer
) {
let packageName = text.split(" ")[1];
await dc.context.sendActivity(
`Deploying package ${packageName}... (It may take a few seconds)`
);
let additionalPath = GBConfigService.get("ADDITIONAL_DEPLOY_PATH");
await deployer.deployPackageFromLocalPath(
UrlJoin(additionalPath, packageName)
);
await dc.context.sendActivity(
`Package ${packageName} deployed... Please run rebuildIndex command.`
);
}
static async rebuildIndexCommand(min: GBMinInstance, dc) {
let search = new AzureSearch(
min.instance.searchKey,
min.instance.searchHost,
min.instance.searchIndex,
min.instance.searchIndexer
);
dc.context.sendActivity("Rebuilding index...");
await search.deleteIndex();
let kbService = new KBService(min.core.sequelize);
await search.createIndex(
kbService.getSearchSchema(min.instance.searchIndex),
"gb"
);
await dc.context.sendActivity("Index rebuilt.");
}
/**
* Setup dialogs flows and define services call.
*
@ -107,101 +80,80 @@ export class AdminDialog extends IGBDialog {
let importer = new GBImporter(min.core);
let deployer = new GBDeployer(min.core, importer);
min.dialogs.add("/adminRat", [
async dc => {
await AdminDialog.refreshAdminToken(min, dc);
// await dc.context.sendActivity(
// `Deploying package ... (It may take a few seconds)`
// );
// await AdminDialog.deployPackageCommand(
// "deployPackage ProjectOnline.gbkb",
// dc,
// deployer,
// min
// );
await dc.endAll();
}
]);
min.dialogs.add("/adminUpdateToken", [
async (dc, args, next) => {
await dc.endAll();
let service = new GBAdminService();
await service.saveValue("authenticatorToken", args.token)
await dc.context.sendActivities([
{ type: 'typing' },
{ type: 'message', text: "Token has been updated." },
{ type: 'message', text: "Please, log out now from the administration work account on next screen." },
{ type: 'delay', value: 4000 },
])
}
]);
min.dialogs.add("/admin", [
async (dc, args) => {
const prompt = "Please, authenticate:";
async dc => {
const locale = dc.context.activity.locale;
const prompt = Messages[locale].authenticate;
await dc.prompt("textPrompt", prompt);
},
async (dc, value) => {
let text = value;
const user = min.userState.get(dc.context);
if (!user.authenticated || text === GBConfigService.get("ADMIN_PASS")) {
user.authenticated = true;
async (dc, password) => {
const locale = dc.context.activity.locale;
if (
password === GBConfigService.get("ADMIN_PASS") &&
GBAdminService.StrongRegex.test(password)
) {
await dc.context.sendActivity(
"Welcome to Pragmatismo.io GeneralBots Administration."
Messages[locale].welcome
);
await dc.prompt("textPrompt", "Which task do you wanna run now?");
await dc.prompt("textPrompt", Messages[locale].which_task);
} else {
await dc.prompt("textPrompt", Messages[locale].wrong_password);
await dc.endAll();
}
},
async (dc, value) => {
const locale = dc.context.activity.locale;
var text = value;
const user = min.userState.get(dc.context);
let cmdName = text.split(" ")[0];
dc.context.sendActivity(Messages[locale].working(cmdName))
if (text === "quit") {
user.authenticated = false;
await dc.replace("/");
} else if (text === "sync") {
await min.core.syncDatabaseStructure();
await dc.context.sendActivity("Sync started...");
} else if (cmdName === "deployPackage") {
await AdminDialog.deployPackageCommand(text, deployer);
await dc.replace("/admin", { firstRun: false });
} else if (text.split(" ")[0] === "rebuildIndex") {
await AdminDialog.rebuildIndexCommand(min, dc);
} else if (cmdName === "redeployPackage") {
await AdminDialog.undeployPackageCommand(text, min);
await AdminDialog.deployPackageCommand(text, deployer);
await dc.context.sendActivity();
await dc.replace("/admin", { firstRun: false });
} else if (text.split(" ")[0] === "deployPackage") {
await AdminDialog.deployPackageCommand(text, dc, deployer, min);
} else if (cmdName === "undeployPackage") {
await AdminDialog.undeployPackageCommand(text, min);
await dc.replace("/admin", { firstRun: false });
} else if (text.split(" ")[0] === "redeployPackage") {
await AdminDialog.undeployPackageCommand(text, min, dc);
await AdminDialog.deployPackageCommand(text, dc, deployer, min);
await dc.context.sendActivity("Redeploy done.");
await dc.replace("/admin", { firstRun: false });
} else if (text.split(" ")[0] === "undeployPackage") {
await AdminDialog.undeployPackageCommand(text, min, dc);
await dc.replace("/admin", { firstRun: false });
} else if (text.split(" ")[0] === "applyPackage") {
await dc.context.sendActivity("Applying in progress...");
await min.core.loadInstance(text.split(" ")[1]);
await dc.context.sendActivity("Applying done...");
await dc.replace("/admin", { firstRun: false });
} else if (text.split(" ")[0] === "rat") {
await AdminDialog.refreshAdminToken(min, dc);
} else if (cmdName === "setupSecurity") {
await AdminDialog.setupSecurity(min, dc);
}
else{
await dc.context.sendActivity(Messages[locale].unknown_command);
dc.endAll()
await dc.replace("/answer", { query: text });
}
}
]);
}
private static async refreshAdminToken(min: any, dc: any) {
let config = {
authenticatorTenant: min.instance.authenticatorTenant,
authenticatorClientID: min.instance.authenticatorClientID
};
await min.conversationalService.sendEvent(dc, "play", {
playerType: "login",
data: config
});
await dc.context.sendActivity("Update your Administrative token by Login...");
private static async setupSecurity(min: any, dc: any) {
const locale = dc.context.activity.locale;
let state = `${min.instance.instanceId}${Math.floor(
Math.random() * 1000000000
)}`;
await min.adminService.setValue(
min.instance.instanceId,
"AntiCSRFAttackState",
state
);
let url = `https://login.microsoftonline.com/${
min.instance.authenticatorTenant
}/oauth2/authorize?client_id=${
min.instance.authenticatorClientId
}&response_type=code&redirect_uri=${min.instance.botServerUrl}/${
min.instance.botId
}/token&state=${state}&response_mode=query`;
await dc.context.sendActivity(
Messages[locale].consent(url)
);
}
}

View file

@ -45,6 +45,9 @@ import {
export class GuaribasAdmin extends Model<GuaribasAdmin>
{
@Column
instanceId: number;
@Column
key: string;

View file

@ -30,28 +30,97 @@
| |
\*****************************************************************************/
"use strict"
"use strict";
import { GuaribasAdmin } from "../models/AdminModel";
import { IGBCoreService } from "botlib";
import { AuthenticationContext, TokenResponse } from "adal-node";
const UrlJoin = require("url-join");
export class GBAdminService {
public static StrongRegex = new RegExp(
"^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,})"
);
async saveValue(key: string, value: string): Promise<GuaribasAdmin> {
let options = { where: {} }
options.where = { key: key }
core: IGBCoreService;
constructor(core: IGBCoreService) {
this.core = core;
}
public async setValue(
instanceId: number,
key: string,
value: string
): Promise<GuaribasAdmin> {
let options = { where: {} };
options.where = { key: key };
let admin = await GuaribasAdmin.findOne(options);
if (admin == null) {
admin = new GuaribasAdmin();
admin.key = key;
}
admin.value = value;
return admin.save()
admin.instanceId = instanceId;
return admin.save();
}
async getValue(key: string) {
let options = { where: {} }
options.where = { key: key }
public async getValue(instanceId: number, key: string) {
let options = { where: {} };
options.where = { key: key, instanceId: instanceId };
let obj = await GuaribasAdmin.findOne(options);
return Promise.resolve(obj.value);
}
public async acquireElevatedToken(instanceId): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
let instance = await this.core.loadInstanceById(instanceId);
let expiresOn = new Date(await this.getValue(instanceId, "expiresOn"));
if (expiresOn.getTime() > new Date().getTime()) {
let accessToken = await this.getValue(instanceId, "accessToken");
resolve(accessToken);
} else {
let authorizationUrl = UrlJoin(
instance.authenticatorAuthorityHostUrl,
instance.authenticatorTenant,
"/oauth2/authorize"
);
var authenticationContext = new AuthenticationContext(authorizationUrl);
let refreshToken = await this.getValue(instanceId, "refreshToken");
let resource = "https://graph.microsoft.com";
authenticationContext.acquireTokenWithRefreshToken(
refreshToken,
instance.authenticatorClientId,
instance.authenticatorClientSecret,
resource,
async (err, res) => {
if (err) {
reject(err);
} else {
let tokens = res as TokenResponse;
await this.setValue(
instanceId,
"accessToken",
tokens.accessToken
);
await this.setValue(
instanceId,
"refreshToken",
tokens.refreshToken
);
await this.setValue(
instanceId,
"expiresOn",
tokens.expiresOn.toString()
);
resolve(tokens.accessToken);
}
}
);
}
});
}
}

View file

@ -0,0 +1,21 @@
export const Messages = {
"en-US": {
authenticate: "Please, authenticate:",
welcome: "Welcome to Pragmatismo.io GeneralBots Administration.",
which_task: "Which task do you wanna run now?",
working:(command)=> `I'm working on ${command}`,
unknown_command: text =>
`Well, but ${text} is not a administrative General Bots command, I will try to search for it.`,
hi: text => `Hello, ${text}.`,
undeployPackage: text => `Undeploying package ${text}...`,
deployPackage: text => `Deploying package ${text}...`,
redeployPackage: text => `Redeploying package ${text}...`,
packageUndeployed: text => `Package ${text} undeployed...`,
consent: (url)=>`Please, consent access to this app at: [Microsoft Online](${url}).`,
wrong_password: "Sorry, wrong password. Please, try again."
},
"pt-BR": {
show_video: "Vou te mostrar um vídeo. Por favor, aguarde...",
hi: msg => `Oi, ${msg}.`
}
};

View file

@ -65,9 +65,9 @@ export class GuaribasInstance extends Model<GuaribasInstance>
@AutoIncrement
@Column
instanceId: number;
@Column
applicationPrincipal: string;
botServerUrl:string;
@Column
whoAmIVideo: string;
@ -109,10 +109,21 @@ export class GuaribasInstance extends Model<GuaribasInstance>
@Column
authenticatorTenant: string;
@Column
authenticatorSignUpSignInPolicy: string;
authenticatorAuthorityHostUrl: string;
@Column
authenticatorClientID: string;
authenticatorClientId: string;
@Column
authenticatorClientSecret: string;
@Column
cloudSubscriptionId: string;
@Column
cloudRegion: string;
@Column
whatsappBotKey: string;

View file

@ -1,4 +1,4 @@
import { IGBInstance } from 'botlib';
import { IGBInstance } from "botlib";
/*****************************************************************************\
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
@ -44,7 +44,6 @@ import { Messages } from "../strings";
import { AzureText } from "pragmatismo-io-framework";
const Nexmo = require("nexmo");
export interface LanguagePickerSettings {
defaultLocale?: string;
supportedLocales?: string[];
@ -62,30 +61,40 @@ export class GBConversationalService implements IGBConversationalService {
}
async sendEvent(dc: any, name: string, value: any): Promise<any> {
const msg = MessageFactory.text("");
msg.value = value;
msg.type = "event";
msg.name = name;
return dc.context.sendActivity(msg);
if (dc.context.activity.channelId === "webchat") {
const msg = MessageFactory.text("");
msg.value = value;
msg.type = "event";
msg.name = name;
return dc.context.sendActivity(msg);
}
}
async sendSms(min: GBMinInstance, mobile: string, text: string): Promise<any> {
async sendSms(
min: GBMinInstance,
mobile: string,
text: string
): Promise<any> {
return new Promise((resolve, reject) => {
const nexmo = new Nexmo({
apiKey: min.instance.smsKey,
apiSecret: min.instance.smsSecret,
apiSecret: min.instance.smsSecret
});
nexmo.message.sendSms(
min.instance.smsServiceNumber,
mobile,
text, (err, data) => {
if (err) { reject(err) } else { resolve(data) }
text,
(err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
}
);
});
}
async routeNLP(dc: any, min: GBMinInstance, text: string): Promise<boolean> {
// Invokes LUIS.

View file

@ -78,7 +78,7 @@ export class GBCoreService implements IGBCoreService {
*/
constructor() {
this.dialect = GBConfigService.get("DATABASE_DIALECT")
this.adminService = new GBAdminService();
this.adminService = new GBAdminService(this)
}
/**
@ -269,6 +269,15 @@ export class GBCoreService implements IGBCoreService {
return GuaribasInstance.findAll({});
}
/**
* Loads just one Bot instance by its internal Id.
*/
async loadInstanceById(instanceId: string): Promise<IGBInstance> {
let options = { where: {instanceId: instanceId} }
return GuaribasInstance.findOne(options);
}
/**
* Loads just one Bot instance.
*/

View file

@ -30,36 +30,37 @@
| |
\*****************************************************************************/
"use strict"
"use strict";
const logger = require("../../../src/logger")
const Path = require("path")
const UrlJoin = require("url-join")
const Fs = require("fs")
const WaitUntil = require("wait-until")
const express = require("express")
const logger = require("../../../src/logger");
const Path = require("path");
const UrlJoin = require("url-join");
const Fs = require("fs");
const WaitUntil = require("wait-until");
const express = require("express");
import { KBService } from "./../../kb.gbapp/services/KBService"
import { GBImporter } from "./GBImporter"
import { IGBCoreService, IGBInstance } from "botlib"
import { GBConfigService } from "./GBConfigService"
import { GBError } from "botlib"
import { GuaribasPackage } from "../models/GBModel"
import { IGBPackage } from "botlib"
import { KBService } from "./../../kb.gbapp/services/KBService";
import { GBImporter } from "./GBImporter";
import { IGBCoreService, IGBInstance } from "botlib";
import { GBConfigService } from "./GBConfigService";
import { GBError } from "botlib";
import { GuaribasPackage, GuaribasInstance } from "../models/GBModel";
import { IGBPackage } from "botlib";
import { AzureSearch } from "pragmatismo-io-framework";
/** Deployer service for bots, themes, ai and more. */
export class GBDeployer {
core: IGBCoreService
core: IGBCoreService;
importer: GBImporter
importer: GBImporter;
workDir: string = "./work"
workDir: string = "./work";
static deployFolder = "deploy"
static deployFolder = "deploy";
constructor(core: IGBCoreService, importer: GBImporter) {
this.core = core
this.importer = importer
this.core = core;
this.importer = importer;
}
/**
@ -72,103 +73,102 @@ export class GBDeployer {
server: any,
appPackages: Array<IGBPackage>
) {
let _this = this
let _this = this;
return new Promise((resolve, reject) => {
try {
let totalPackages = 0
let additionalPath = GBConfigService.get("ADDITIONAL_DEPLOY_PATH")
let paths = [GBDeployer.deployFolder]
let totalPackages = 0;
let additionalPath = GBConfigService.get("ADDITIONAL_DEPLOY_PATH");
let paths = [GBDeployer.deployFolder];
if (additionalPath) {
paths = paths.concat(additionalPath.toLowerCase().split(";"))
paths = paths.concat(additionalPath.toLowerCase().split(";"));
}
let botPackages = new Array<string>()
let gbappPackages = new Array<string>()
let generalPackages = new Array<string>()
let botPackages = new Array<string>();
let gbappPackages = new Array<string>();
let generalPackages = new Array<string>();
function doIt(path) {
const isDirectory = source => Fs.lstatSync(source).isDirectory()
const isDirectory = source => Fs.lstatSync(source).isDirectory();
const getDirectories = source =>
Fs.readdirSync(source)
.map(name => Path.join(source, name))
.filter(isDirectory)
.filter(isDirectory);
let dirs = getDirectories(path)
let dirs = getDirectories(path);
dirs.forEach(element => {
if (element.startsWith(".")) {
logger.info(`Ignoring ${element}...`)
logger.info(`Ignoring ${element}...`);
} else {
if (element.endsWith(".gbot")) {
botPackages.push(element)
botPackages.push(element);
} else if (element.endsWith(".gbapp")) {
gbappPackages.push(element)
gbappPackages.push(element);
} else {
generalPackages.push(element)
generalPackages.push(element);
}
}
})
});
}
logger.info(
`Starting looking for packages (.gbot, .gbtheme, .gbkb, .gbapp)...`
)
);
paths.forEach(e => {
logger.info(`Looking in: ${e}...`)
doIt(e)
})
logger.info(`Looking in: ${e}...`);
doIt(e);
});
/** Deploys all .gbapp files first. */
let appPackagesProcessed = 0
let appPackagesProcessed = 0;
gbappPackages.forEach(e => {
logger.info(`Deploying app: ${e}...`)
// Skips .gbapp inside deploy folder.
if (!e.startsWith("deploy")) {
logger.info(`Deploying app: ${e}...`);
import(e)
.then(m => {
let p = new m.Package()
p.loadPackage(core, core.sequelize)
appPackages.push(p)
logger.info(`App (.gbapp) deployed: ${e}.`)
appPackagesProcessed++
let p = new m.Package();
p.loadPackage(core, core.sequelize);
appPackages.push(p);
logger.info(`App (.gbapp) deployed: ${e}.`);
appPackagesProcessed++;
})
.catch(err => {
logger.error(`Error deploying App (.gbapp): ${e}: ${err}`)
appPackagesProcessed++
})
logger.error(`Error deploying App (.gbapp): ${e}: ${err}`);
appPackagesProcessed++;
});
} else {
appPackagesProcessed++
appPackagesProcessed++;
}
})
});
WaitUntil()
.interval(1000)
.times(10)
.condition(function (cb) {
logger.info(`Waiting for app package deployment...`)
cb(appPackagesProcessed == gbappPackages.length)
.condition(function(cb) {
logger.info(`Waiting for app package deployment...`);
cb(appPackagesProcessed == gbappPackages.length);
})
.done(function (result) {
.done(async result => {
logger.info(`App Package deployment done.`);
(async () => {
await core.syncDatabaseStructure()
})()
await core.syncDatabaseStructure();
/** Deploys all .gbot files first. */
botPackages.forEach(e => {
logger.info(`Deploying bot: ${e}...`)
_this.deployBot(e)
logger.info(`Bot: ${e} deployed...`)
})
logger.info(`Deploying bot: ${e}...`);
_this.deployBot(e);
logger.info(`Bot: ${e} deployed...`);
});
/** Then all remaining generalPackages are loaded. */
generalPackages = generalPackages.filter(p => !p.endsWith(".git"));
generalPackages.forEach(filename => {
let filenameOnly = Path.basename(filename)
logger.info(`Deploying package: ${filename}...`)
let filenameOnly = Path.basename(filename);
logger.info(`Deploying package: ${filename}...`);
/** Handles apps for general bots - .gbapp must stay out of deploy folder. */
@ -178,57 +178,54 @@ export class GBDeployer {
) {
/** Themes for bots. */
} else if (Path.extname(filename) === ".gbtheme") {
server.use("/themes/" + filenameOnly, express.static(filename))
server.use("/themes/" + filenameOnly, express.static(filename));
logger.info(
`Theme (.gbtheme) assets accessible at: ${"/themes/" +
filenameOnly}.`
)
filenameOnly}.`
);
/** Knowledge base for bots. */
} else if (Path.extname(filename) === ".gbkb") {
server.use(
"/kb/" + filenameOnly + "/subjects",
express.static(UrlJoin(filename, "subjects"))
)
);
logger.info(
`KB (.gbkb) assets accessible at: ${"/kb/" + filenameOnly}.`
)
} else if (
Path.extname(filename) === ".gbui" ||
filename.endsWith(".git")
) {
);
} else if (Path.extname(filename) === ".gbui") {
// Already Handled
} else {
/** Unknown package format. */
let err = new Error(`Package type not handled: ${filename}.`)
reject(err)
let err = new Error(`Package type not handled: ${filename}.`);
reject(err);
}
totalPackages++
})
totalPackages++;
});
WaitUntil()
.interval(100)
.times(5)
.condition(function (cb) {
logger.info(`Waiting for package deployment...`)
cb(totalPackages == generalPackages.length)
.condition(function(cb) {
logger.info(`Waiting for package deployment...`);
cb(totalPackages == generalPackages.length);
})
.done(function (result) {
.done(function(result) {
if (botPackages.length === 0) {
logger.info(
"The server is running with no bot instances, at least one .gbot file must be deployed."
)
);
} else {
logger.info(`Package deployment done.`)
logger.info(`Package deployment done.`);
}
resolve()
})
})
resolve();
});
});
} catch (err) {
logger.error(err)
reject(err)
logger.error(err);
reject(err);
}
})
});
}
/**
@ -236,13 +233,13 @@ export class GBDeployer {
*/
async deployBot(localPath: string): Promise<IGBInstance> {
let packageType = Path.extname(localPath)
let packageName = Path.basename(localPath)
let packageType = Path.extname(localPath);
let packageName = Path.basename(localPath);
let instance = await this.importer.importIfNotExistsBotPackage(
packageName,
localPath
)
return instance
);
return instance;
}
async deployPackageToStorage(
@ -252,7 +249,7 @@ export class GBDeployer {
return GuaribasPackage.create({
packageName: packageName,
instanceId: instanceId
})
});
}
deployTheme(localPath: string) {
@ -268,71 +265,86 @@ export class GBDeployer {
}
async deployPackageFromLocalPath(localPath: string) {
let packageType = Path.extname(localPath)
let packageType = Path.extname(localPath);
switch (packageType) {
case ".gbot":
return this.deployBot(localPath)
return this.deployBot(localPath);
case ".gbtheme":
return this.deployTheme(localPath)
return this.deployTheme(localPath);
// PACKAGE: Put in package logic.
case ".gbkb":
let service = new KBService(this.core.sequelize)
return service.deployKb(this.core, this, localPath)
let service = new KBService(this.core.sequelize);
return service.deployKb(this.core, this, localPath);
case ".gbui":
break
break;
default:
var err = GBError.create(
`GuaribasBusinessError: Unknow package type: ${packageType}.`
)
Promise.reject(err)
break
);
Promise.reject(err);
break;
}
}
async undeployPackageFromLocalPath(instance: IGBInstance, localPath: string) {
let packageType = Path.extname(localPath)
let packageName = Path.basename(localPath)
let packageType = Path.extname(localPath);
let packageName = Path.basename(localPath);
let p = await this.getPackageByName(instance.instanceId, packageName)
let p = await this.getPackageByName(instance.instanceId, packageName);
switch (packageType) {
case ".gbot":
// TODO: this.undeployBot(packageName, localPath)
break
break;
case ".gbtheme":
// TODO: this.undeployTheme(packageName, localPath)
break
break;
case ".gbkb":
let service = new KBService(this.core.sequelize)
return service.undeployKbFromStorage(instance, p.packageId)
let service = new KBService(this.core.sequelize);
return service.undeployKbFromStorage(instance, this, p.packageId);
case ".gbui":
break
break;
default:
var err = GBError.create(
`GuaribasBusinessError: Unknown package type: ${packageType}.`
)
Promise.reject(err)
break
);
Promise.reject(err);
break;
}
}
public async rebuildIndex(instance: GuaribasInstance) {
let search = new AzureSearch(
instance.searchKey,
instance.searchHost,
instance.searchIndex,
instance.searchIndexer
);
await search.deleteIndex();
let kbService = new KBService(this.core.sequelize);
await search.createIndex(
kbService.getSearchSchema(instance.searchIndex),
"gb"
);
}
async getPackageByName(
instanceId: number,
packageName: string
): Promise<GuaribasPackage> {
var where = { packageName: packageName, instanceId: instanceId }
var where = { packageName: packageName, instanceId: instanceId };
return GuaribasPackage.findOne({
where: where
})
});
}
/**
@ -341,15 +353,15 @@ export class GBDeployer {
*
*/
async scanBootPackage() {
const deployFolder = "deploy"
let bootPackage = GBConfigService.get("BOOT_PACKAGE")
const deployFolder = "deploy";
let bootPackage = GBConfigService.get("BOOT_PACKAGE");
if (bootPackage === "none") {
return Promise.resolve(true)
return Promise.resolve(true);
} else {
return this.deployPackageFromLocalPath(
UrlJoin(deployFolder, bootPackage)
)
);
}
}
}

View file

@ -30,13 +30,15 @@
| |
\*****************************************************************************/
"use strict"
"use strict";
const { TextPrompt } = require("botbuilder-dialogs")
const UrlJoin = require("url-join")
const express = require("express")
const logger = require("../../../src/logger")
const request = require('request-promise-native')
const { TextPrompt } = require("botbuilder-dialogs");
const UrlJoin = require("url-join");
const express = require("express");
const logger = require("../../../src/logger");
const request = require("request-promise-native");
var crypto = require("crypto");
var AuthenticationContext = require("adal-node").AuthenticationContext;
import {
BotFrameworkAdapter,
@ -44,28 +46,32 @@ import {
ConversationState,
MemoryStorage,
UserState
} from "botbuilder"
} from "botbuilder";
import { GBCoreService } from "./GBCoreService"
import { GBConversationalService } from "./GBConversationalService"
import { GBMinInstance, IGBPackage } from "botlib"
import { GBAnalyticsPackage } from "../../analytics.gblib"
import { GBCorePackage } from "../../core.gbapp"
import { GBKBPackage } from "../../kb.gbapp"
import { GBDeployer } from "./GBDeployer"
import { GBSecurityPackage } from "../../security.gblib"
import { GBAdminPackage } from "./../../admin.gbapp/index"
import { GBCustomerSatisfactionPackage } from "../../customer-satisfaction.gbapp"
import { GBWhatsappPackage } from "../../whatsapp.gblib"
import { GBMinInstance, IGBPackage } from "botlib";
import { GBAnalyticsPackage } from "../../analytics.gblib";
import { GBCorePackage } from "../../core.gbapp";
import { GBKBPackage } from "../../kb.gbapp";
import { GBDeployer } from "./GBDeployer";
import { GBSecurityPackage } from "../../security.gblib";
import { GBAdminPackage } from "./../../admin.gbapp/index";
import { GBCustomerSatisfactionPackage } from "../../customer-satisfaction.gbapp";
import { GBWhatsappPackage } from "../../whatsapp.gblib";
import {
IGBAdminService,
IGBCoreService,
IGBConversationalService
} from "botlib";
/** Minimal service layer for a bot. */
export class GBMinService {
core: GBCoreService
conversationalService: GBConversationalService
deployer: GBDeployer
core: IGBCoreService;
conversationalService: IGBConversationalService;
adminService: IGBAdminService;
deployer: GBDeployer;
corePackage = "core.gbai"
corePackage = "core.gbai";
/**
* Static initialization of minimal instance.
@ -73,13 +79,15 @@ export class GBMinService {
* @param core Basic database services to identify instance, for example.
*/
constructor(
core: GBCoreService,
conversationalService: GBConversationalService,
core: IGBCoreService,
conversationalService: IGBConversationalService,
adminService: IGBAdminService,
deployer: GBDeployer
) {
this.core = core
this.conversationalService = conversationalService
this.deployer = deployer
this.core = core;
this.conversationalService = conversationalService;
this.adminService = adminService;
this.deployer = deployer;
}
/**
@ -97,37 +105,34 @@ export class GBMinService {
server: any,
appPackages: Array<IGBPackage>
): Promise<GBMinInstance> {
// Serves default UI on root address '/'.
let uiPackage = "default.gbui"
let uiPackage = "default.gbui";
server.use(
"/",
express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, "build"))
)
);
// Loads all bot instances from storage and starting loading them.
let instances = await this.core.loadInstances()
let instances = await this.core.loadInstances();
Promise.all(
instances.map(async instance => {
// Gets the authorization key for each instance from Bot Service.
let webchatToken = await this.getWebchatToken(instance)
let webchatToken = await this.getWebchatToken(instance);
// Serves the bot information object via HTTP so clients can get
// instance information stored on server.
server.get("/instances/:botId", (req, res) => {
(async () => {
// Returns the instance object to clients requesting bot info.
let botId = req.params.botId
let instance = await this.core.loadInstance(botId)
let botId = req.params.botId;
let instance = await this.core.loadInstance(botId);
if (instance) {
let speechToken = await this.getSTSToken(instance)
let speechToken = await this.getSTSToken(instance);
res.send(
JSON.stringify({
@ -138,30 +143,30 @@ export class GBMinService {
speechToken: speechToken,
conversationId: webchatToken.conversationId,
authenticatorTenant: instance.authenticatorTenant,
authenticatorClientID: instance.authenticatorClientID
authenticatorClientId: instance.authenticatorClientId
})
)
);
} else {
let error = `Instance not found: ${botId}.`
res.sendStatus(error)
logger.error(error)
let error = `Instance not found: ${botId}.`;
res.sendStatus(error);
logger.error(error);
}
})()
})
})();
});
// Build bot adapter.
var { min, adapter, conversationState } = await this.buildBotAdapter(
instance
)
);
// Call the loadBot context.activity for all packages.
this.invokeLoadBot(appPackages, min, server)
this.invokeLoadBot(appPackages, min, server);
// Serves individual URL for each bot conversational interface...
let url = `/api/messages/${instance.botId}`
let url = `/api/messages/${instance.botId}`;
server.post(url, async (req, res) => {
return this.receiver(
adapter,
@ -171,20 +176,107 @@ export class GBMinService {
min,
instance,
appPackages
)
})
);
});
logger.info(
`GeneralBots(${instance.engineName}) listening on: ${url}.`
)
);
// Serves individual URL for each bot user interface.
let uiUrl = `/${instance.botId}`
let uiUrl = `/${instance.botId}`;
server.use(
uiUrl,
express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, "build"))
)
logger.info(`Bot UI ${uiPackage} accessible at: ${uiUrl}.`)
);
logger.info(`Bot UI ${uiPackage} accessible at: ${uiUrl}.`);
let state = `${instance.instanceId}${Math.floor(
Math.random() * 1000000000
)}`;
// Clients get redirected here in order to create an OAuth authorize url and redirect them to AAD.
// There they will authenticate and give their consent to allow this app access to
// some resource they own.
server.get(`/${min.instance.botId}/auth`, function(req, res) {
let authorizationUrl = UrlJoin(
min.instance.authenticatorAuthorityHostUrl,
min.instance.authenticatorTenant,
"/oauth2/authorize"
);
authorizationUrl = `${authorizationUrl}?response_type=code&client_id=${
min.instance.authenticatorClientId
}&redirect_uri=${min.instance.botServerUrl}/${
min.instance.botId
}/token`;
res.redirect(authorizationUrl);
});
// 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.
server.get(`/${min.instance.botId}/token`, async (req, res) => {
let state = await min.adminService.getValue(
min.instance.instanceId,
"AntiCSRFAttackState"
);
if (req.query.state != state) {
let msg =
"WARNING: state field was not provided as anti-CSRF token";
logger.error(msg);
throw new Error(msg);
}
var authenticationContext = new AuthenticationContext(
UrlJoin(
min.instance.authenticatorAuthorityHostUrl,
min.instance.authenticatorTenant
)
);
let resource = "https://graph.microsoft.com";
authenticationContext.acquireTokenWithAuthorizationCode(
req.query.code,
UrlJoin(instance.botServerUrl, min.instance.botId, "/token"),
resource,
instance.authenticatorClientId,
instance.authenticatorClientSecret,
async (err, token) => {
if (err) {
let msg = `Error acquiring token: ${err}`;
logger.error(msg);
res.send(msg);
} else {
await this.adminService.setValue(
instance.instanceId,
"refreshToken",
token.refreshToken
);
await this.adminService.setValue(
instance.instanceId,
"accessToken",
token.accessToken
);
await this.adminService.setValue(
instance.instanceId,
"expiresOn",
token.expiresOn.toString()
);
await this.adminService.setValue(
instance.instanceId,
"AntiCSRFAttackState",
null
);
res.redirect(min.instance.botServerUrl);
}
}
);
});
// Setups handlers.
// send: function (context.activity, next) {
@ -201,32 +293,33 @@ export class GBMinService {
// )
// next()
})
)
);
}
private async buildBotAdapter(instance: any) {
let adapter = new BotFrameworkAdapter({
appId: instance.marketplaceId,
appPassword: instance.marketplacePassword
})
});
const storage = new MemoryStorage()
const conversationState = new ConversationState(storage)
const userState = new UserState(storage)
adapter.use(new BotStateSet(conversationState, userState))
const storage = new MemoryStorage();
const conversationState = new ConversationState(storage);
const userState = new UserState(storage);
adapter.use(new BotStateSet(conversationState, userState));
// The minimal bot is built here.
let min = new GBMinInstance()
min.botId = instance.botId
min.bot = adapter
min.userState = userState
min.core = this.core
min.conversationalService = this.conversationalService
min.instance = await this.core.loadInstance(min.botId)
min.dialogs.add("textPrompt", new TextPrompt())
let min = new GBMinInstance();
min.botId = instance.botId;
min.bot = adapter;
min.userState = userState;
min.core = this.core;
min.conversationalService = this.conversationalService;
min.adminService = this.adminService;
min.instance = await this.core.loadInstance(min.botId);
min.dialogs.add("textPrompt", new TextPrompt());
return { min, adapter, conversationState }
return { min, adapter, conversationState };
}
private invokeLoadBot(appPackages: any[], min: any, server: any) {
@ -244,19 +337,18 @@ export class GBMinService {
GBCustomerSatisfactionPackage,
GBWhatsappPackage
].forEach(sysPackage => {
logger.info(`Loading sys package: ${sysPackage.name}...`)
let p = Object.create(sysPackage.prototype) as IGBPackage
p.loadBot(min)
e.sysPackages.push(p)
let p = Object.create(sysPackage.prototype) as IGBPackage;
p.loadBot(min);
e.sysPackages.push(p);
if (sysPackage.name === "GBWhatsappPackage") {
let url = "/instances/:botId/whatsapp"
let url = "/instances/:botId/whatsapp";
server.post(url, (req, res) => {
p["channel"].received(req, res)
})
p["channel"].received(req, res);
});
}
}, this)
e.loadBot(min)
}, this)
}, this);
e.loadBot(min);
}, this);
}
/**
@ -272,12 +364,11 @@ export class GBMinService {
appPackages: any[]
) {
return adapter.processActivity(req, res, async context => {
try {
const state = conversationState.get(context)
const dc = min.dialogs.createContext(context, state)
dc.context.activity.locale = "en-US"
const user = min.userState.get(dc.context)
const state = conversationState.get(context);
const dc = min.dialogs.createContext(context, state);
dc.context.activity.locale = "en-US";
const user = min.userState.get(dc.context);
if (!user.loaded) {
await min.conversationalService.sendEvent(dc, "loadInstance", {
@ -285,99 +376,95 @@ export class GBMinService {
botId: instance.botId,
theme: instance.theme,
secret: instance.webchatKey
})
user.loaded = true
user.subjects = []
});
user.loaded = true;
user.subjects = [];
}
logger.info(
`[User]: ${context.activity.type}, ChannelID: ${
context.activity.channelId
} Text: ${context.activity.text}.`
)
`User>: ${context.activity.text} (${context.activity.type}, ${
context.activity.name
}, ${context.activity.channelId}, {context.activity.value})`
);
if (
context.activity.type === "conversationUpdate" &&
context.activity.membersAdded.length > 0
) {
let member = context.activity.membersAdded[0]
let member = context.activity.membersAdded[0];
if (member.name === "GeneralBots") {
logger.info(`Bot added to conversation, starting chat...`)
logger.info(`Bot added to conversation, starting chat...`);
appPackages.forEach(e => {
e.onNewSession(min, dc)
})
e.onNewSession(min, dc);
});
// Processes the root dialog.
await dc.begin("/")
await dc.begin("/");
} else {
logger.info(`Member added to conversation: ${member.name}`)
logger.info(`Member added to conversation: ${member.name}`);
}
// Processes messages.
} else if (context.activity.type === "message") {
// Checks for /admin request.
if (context.activity.text === "admin") {
await dc.begin("/admin")
await dc.begin("/admin");
// Checks for /menu JSON signature.
} else if (context.activity.text.startsWith("{\"title\"")) {
await dc.begin("/menu", { data: JSON.parse(context.activity.text) })
} else if (context.activity.text.startsWith('{"title"')) {
await dc.begin("/menu", {
data: JSON.parse(context.activity.text)
});
// Otherwise, continue to the active dialog in the stack.
} else {
if (dc.activeDialog) {
await dc.continue()
await dc.continue();
} else {
await dc.begin("/answer", {query: context.activity.text})
await dc.begin("/answer", { query: context.activity.text });
}
}
// Processes events.
} else if (context.activity.type === "event") {
// Empties dialog stack before going to the target.
await dc.endAll()
await dc.endAll();
if (context.activity.name === "whoAmI") {
await dc.begin("/whoAmI")
await dc.begin("/whoAmI");
} else if (context.activity.name === "showSubjects") {
await dc.begin("/menu")
await dc.begin("/menu");
} else if (context.activity.name === "giveFeedback") {
await dc.begin("/feedback", {
fromMenu: true
})
});
} else if (context.activity.name === "showFAQ") {
await dc.begin("/faq")
await dc.begin("/faq");
} else if (context.activity.name === "answerEvent") {
await dc.begin("/answerEvent", {
questionId: (context.activity as any).data,
fromFaq: true
})
});
} else if (context.activity.name === "quality") {
await dc.begin("/quality", { score: (context.activity as any).data })
await dc.begin("/quality", {
score: (context.activity as any).data
});
} else if (context.activity.name === "updateToken") {
let token = (context.activity as any).data
await dc.begin("/adminUpdateToken", { token: token })
let token = (context.activity as any).data;
await dc.begin("/adminUpdateToken", { token: token });
} else {
await dc.continue()
await dc.continue();
}
}
} catch (error) {
let msg = `Error in main activity: ${error.message} ${error.stack? error.stack:""}`
logger.error(msg)
let msg = `Error in main activity: ${error.message} ${
error.stack ? error.stack : ""
}`;
logger.error(msg);
}
})
});
}
/**
@ -393,15 +480,15 @@ export class GBMinService {
headers: {
Authorization: `Bearer ${instance.webchatKey}`
}
}
};
try {
let json = await request(options)
return Promise.resolve(JSON.parse(json))
let json = await request(options);
return Promise.resolve(JSON.parse(json));
} catch (error) {
let msg = `Error calling Direct Line client, verify Bot endpoint on the cloud. Error is: ${error}.`
logger.error(msg)
return Promise.reject(msg)
let msg = `Error calling Direct Line client, verify Bot endpoint on the cloud. Error is: ${error}.`;
logger.error(msg);
return Promise.reject(msg);
}
}
@ -420,14 +507,14 @@ export class GBMinService {
headers: {
"Ocp-Apim-Subscription-Key": instance.speechKey
}
}
};
try {
return await request(options)
return await request(options);
} catch (error) {
let msg = `Error calling Speech to Text client. Error is: ${error}.`
logger.error(msg)
return Promise.reject(msg)
let msg = `Error calling Speech to Text client. Error is: ${error}.`;
logger.error(msg);
return Promise.reject(msg);
}
}
}

View file

@ -149,7 +149,7 @@ class GBUIApp extends React.Component {
let graphScopes = ["Directory.AccessAsUser.All"];
let userAgentApplication = new UserAgentApplication(
this.state.instanceClient.authenticatorClientID,
this.state.instanceClient.authenticatorClientId,
authority,
function(errorDesc, token, error, tokenType) {
if (error) {

View file

@ -58,7 +58,7 @@ class GBLoginPlayer extends React.Component {
let graphScopes = ["Directory.AccessAsUser.All"];
let userAgentApplication = new UserAgentApplication(
this.state.login.authenticatorClientID,
this.state.login.authenticatorClientId,
authority,
function (errorDesc, token, error, tokenType) {
if (error) {

View file

@ -119,14 +119,14 @@ class GBMarkdownPlayer extends Component {
}
if (this.state.prevId) {
prev = <a style={{ color: 'blue' }}
onPress={() => this.sendAnswer(this.state.prevId)}>
prev = <a style={{ color: 'blue', cursor: 'pointer' }}
onClick={() => this.sendAnswer(this.state.prevId)}>
Back
</a>
}
if (this.state.nextId) {
next = <a style={{ color: 'blue' }}
onPress={() => this.sendAnswer(this.state.nextId)}>
next = <a style={{ color: 'blue', cursor: 'pointer' }}
onClick={() => this.sendAnswer(this.state.nextId)}>
Next
</a>
}

View file

@ -74,6 +74,8 @@ export class AskDialog extends IGBDialog {
dc,
answer
);
await dc.replace("/ask", { isReturning: true });
}
}])

View file

@ -442,7 +442,7 @@ export class KBService {
}
async sendAnswer(conversationalService: IGBConversationalService,
dc: any, answer: GuaribasAnswer): Promise<any> {
dc: any, answer: GuaribasAnswer) {
if (answer.content.endsWith('.mp4')) {
await conversationalService.sendEvent(dc, "play", {
@ -560,6 +560,7 @@ export class KBService {
async undeployKbFromStorage(
instance: IGBInstance,
deployer: GBDeployer,
packageId: number
) {
@ -576,8 +577,7 @@ export class KBService {
where: { instanceId: instance.instanceId, packageId: packageId }
})
return Promise.resolve()
await deployer.rebuildIndex(instance)
}
/**
@ -599,6 +599,8 @@ export class KBService {
instance.instanceId,
packageName)
await this.importKbPackage(localPath, p, instance)
deployer.rebuildIndex(instance)
logger.info(`[GBDeployer] Finished import of ${localPath}`)
}
}

View file

@ -48,20 +48,22 @@ export class GBSecurityPackage implements IGBPackage {
GuaribasUser,
GuaribasUserGroup
])
core
}
unloadPackage(core: IGBCoreService): void {
}
loadBot(min: GBMinInstance): void {
}
unloadBot(min: GBMinInstance): void {
}
onNewSession(min: GBMinInstance, dc: any): void {
}
}

View file

@ -1,6 +1,6 @@
{
"name": "botserver",
"version": "0.1.2",
"version": "0.1.3",
"description": "General Bot Community Edition open-core server.",
"main": "./src/app.ts",
"homepage": "http://www.generalbot.com",
@ -30,6 +30,8 @@
"node": ">=8.9.4"
},
"dependencies": {
"@microsoft/microsoft-graph-client": "^1.3.0",
"adal-node": "^0.1.28",
"async": "^2.6.1",
"async-promises": "^0.2.1",
"body-parser": "^1.18.3",
@ -39,7 +41,7 @@
"botbuilder-choices": "^4.0.0-preview1.2",
"botbuilder-dialogs": "^4.0.0-preview1.2",
"botbuilder-prompts": "^4.0.0-preview1.2",
"botlib": "^0.1.0",
"botlib": "^0.1.1",
"chokidar": "^2.0.4",
"csv-parse": "^3.0.0",
"dotenv-extended": "^2.3.0",

View file

@ -31,100 +31,123 @@
| |
\*****************************************************************************/
"use strict"
"use strict";
const UrlJoin = require("url-join")
const logger = require("./logger")
const express = require("express")
const bodyParser = require("body-parser")
const UrlJoin = require("url-join");
const logger = require("./logger");
const express = require("express");
const bodyParser = require("body-parser");
const MicrosoftGraph = require("@microsoft/microsoft-graph-client");
import { Sequelize } from "sequelize-typescript"
import { GBConfigService } from "../deploy/core.gbapp/services/GBConfigService"
import { GBConversationalService } from "../deploy/core.gbapp/services/GBConversationalService"
import { GBMinService } from "../deploy/core.gbapp/services/GBMinService"
import { GBDeployer } from "../deploy/core.gbapp/services/GBDeployer"
import { GBWhatsappPackage } from './../deploy/whatsapp.gblib/index'
import { GBCoreService } from "../deploy/core.gbapp/services/GBCoreService"
import { GBImporter } from "../deploy/core.gbapp/services/GBImporter"
import { GBAnalyticsPackage } from "../deploy/analytics.gblib"
import { GBCorePackage } from "../deploy/core.gbapp"
import { GBKBPackage } from '../deploy/kb.gbapp'
import { GBSecurityPackage } from '../deploy/security.gblib'
import { GBAdminPackage } from '../deploy/admin.gbapp/index'
import { GBCustomerSatisfactionPackage } from "../deploy/customer-satisfaction.gbapp"
import { IGBPackage } from 'botlib'
import { Sequelize } from "sequelize-typescript";
import { GBConfigService } from "../deploy/core.gbapp/services/GBConfigService";
import { GBConversationalService } from "../deploy/core.gbapp/services/GBConversationalService";
import { GBMinService } from "../deploy/core.gbapp/services/GBMinService";
import { GBDeployer } from "../deploy/core.gbapp/services/GBDeployer";
import { GBWhatsappPackage } from "./../deploy/whatsapp.gblib/index";
import { GBCoreService } from "../deploy/core.gbapp/services/GBCoreService";
import { GBImporter } from "../deploy/core.gbapp/services/GBImporter";
import { GBAnalyticsPackage } from "../deploy/analytics.gblib";
import { GBCorePackage } from "../deploy/core.gbapp";
import { GBKBPackage } from "../deploy/kb.gbapp";
import { GBSecurityPackage } from "../deploy/security.gblib";
import { GBAdminPackage } from "../deploy/admin.gbapp/index";
import { GBCustomerSatisfactionPackage } from "../deploy/customer-satisfaction.gbapp";
import { IGBPackage } from "botlib";
import { GBAdminService } from "../deploy/admin.gbapp/services/GBAdminService";
let appPackages = new Array<IGBPackage>()
let appPackages = new Array<IGBPackage>();
/**
* General Bots open-core entry point.
*/
export class GBServer {
/** Program entry-point. */
static run() {
// Creates a basic HTTP server that will serve several URL, one for each
// bot instance. This allows the same server to attend multiple Bot on
// the Marketplace until GB get serverless.
let port = process.env.port || process.env.PORT || 4242
logger.info(`The Bot Server is in STARTING mode...`)
let server = express()
let port = process.env.port || process.env.PORT || 4242;
logger.info(`The Bot Server is in STARTING mode...`);
let server = express();
server.use(bodyParser.json()) // to support JSON-encoded bodies
server.use(bodyParser.urlencoded({ // to support URL-encoded bodies
extended: true
}))
server.use(bodyParser.json()); // to support JSON-encoded bodies
server.use(
bodyParser.urlencoded({
// to support URL-encoded bodies
extended: true
})
);
server.listen(port, () => {
(async () => {
try {
logger.info(`Accepting connections on ${port}...`)
logger.info(`Accepting connections on ${port}...`);
// Reads basic configuration, initialize minimal services.
GBConfigService.init()
let core = new GBCoreService()
await core.initDatabase()
GBConfigService.init();
let core = new GBCoreService();
await core.initDatabase();
// Boot a bot package if any.
logger.info(`Starting instances...`)
let deployer = new GBDeployer(core, new GBImporter(core))
logger.info(`Starting instances...`);
let deployer = new GBDeployer(core, new GBImporter(core));
// Build a minimal bot instance for each .gbot deployment.
let conversationalService = new GBConversationalService(core)
let minService = new GBMinService(core, conversationalService, deployer);
let conversationalService = new GBConversationalService(core);
let adminService = new GBAdminService(core);
let password = GBConfigService.get("ADMIN_PASS");
if (!GBAdminService.StrongRegex.test(password)) {
throw new Error(
"STOP: Please, define a really strong password in ADMIN_PASS environment variable before running the server."
);
}
let minService = new GBMinService(
core,
conversationalService,
adminService,
deployer
);
// NOTE: the semicolon is necessary before this line.
// Loads all system packages.
[GBAdminPackage, GBAnalyticsPackage, GBCorePackage, GBSecurityPackage,
GBKBPackage, GBCustomerSatisfactionPackage, GBWhatsappPackage].forEach(e => {
logger.info(`Loading sys package: ${e.name}...`)
let p = Object.create(e.prototype) as IGBPackage
p.loadPackage(core, core.sequelize)
})
[
GBAdminPackage,
GBAnalyticsPackage,
GBCorePackage,
GBSecurityPackage,
GBKBPackage,
GBCustomerSatisfactionPackage,
GBWhatsappPackage
].forEach(e => {
logger.info(`Loading sys package: ${e.name}...`);
let p = Object.create(e.prototype) as IGBPackage;
p.loadPackage(core, core.sequelize);
});
await deployer.deployPackages(core, server, appPackages)
logger.info(`The Bot Server is in RUNNING mode...`)
let instance = await minService.buildMin(server, appPackages)
logger.info(`Instance loaded: ${instance.botId}...`)
return core
logger.info(`Deploying packages.`);
await deployer.deployPackages(core, server, appPackages);
logger.info(`Building minimal instances.`);
await minService.buildMin(server, appPackages);
logger.info(`All instances are now loaded and available.`);
logger.info(`The Bot Server is in RUNNING mode...`);
return core;
} catch (err) {
logger.info(err)
logger.info(`STOP: ${err} ${err.stack ? err.stack : ""}`);
}
})()
})
})();
});
}
}
// First line to run.
GBServer.run()
GBServer.run();