* 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:
parent
7375f179b2
commit
3832f27451
19 changed files with 678 additions and 461 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,6 +45,9 @@ import {
|
|||
export class GuaribasAdmin extends Model<GuaribasAdmin>
|
||||
{
|
||||
|
||||
@Column
|
||||
instanceId: number;
|
||||
|
||||
@Column
|
||||
key: string;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
21
deploy/admin.gbapp/strings.ts
Normal file
21
deploy/admin.gbapp/strings.ts
Normal 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}.`
|
||||
}
|
||||
};
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
|
|
|
@ -74,6 +74,8 @@ export class AskDialog extends IGBDialog {
|
|||
dc,
|
||||
answer
|
||||
);
|
||||
|
||||
await dc.replace("/ask", { isReturning: true });
|
||||
}
|
||||
}])
|
||||
|
||||
|
|
|
@ -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}`)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
137
src/app.ts
137
src/app.ts
|
@ -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();
|
||||
|
|
Loading…
Add table
Reference in a new issue