diff --git a/package.json b/package.json index 43c15e8bf..8b774d821 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,9 @@ "@microsoft/microsoft-graph-client": "3.0.4", "@nlpjs/basic": "4.26.1", "@nosferatu500/textract": "3.1.2", + "@push-rpc/core": "^1.5.7", + "@push-rpc/http": "^1.5.7", + "@push-rpc/websocket": "^1.5.7", "@semantic-release/changelog": "5.0.1", "@semantic-release/exec": "5.0.0", "@semantic-release/git": "9.0.0", @@ -171,6 +174,7 @@ "whatsapp-web.js": "github:pedroslopez/whatsapp-web.js#fix-buttons-list", "winston": "3.8.2", "winston-logs-display": "1.0.0", + "ws": "^8.12.1", "yarn": "1.22.19" }, "devDependencies": { diff --git a/packages/basic.gblib/index.ts b/packages/basic.gblib/index.ts index b4d3eded7..9e05e11c8 100644 --- a/packages/basic.gblib/index.ts +++ b/packages/basic.gblib/index.ts @@ -39,16 +39,43 @@ import { GBDialogStep, GBLog, GBMinInstance, IGBCoreService, IGBPackage } from 'botlib'; import { GuaribasSchedule } from '../core.gbapp/models/GBModel.js'; import { Sequelize } from 'sequelize-typescript'; -import { createServerRouter } from 'typescript-rest-rpc/lib/server.js'; import { DialogKeywords } from './services/DialogKeywords.js'; import { SystemKeywords } from './services/SystemKeywords.js'; import { WebAutomationServices } from './services/WebAutomationServices.js'; import { ImageProcessingServices } from './services/ImageProcessingServices.js'; import { DebuggerService } from './services/DebuggerService.js'; -import * as koaBody from 'koa-body'; import Koa from 'koa'; - +import {createRpcServer, createRpcClient} from "@push-rpc/core" +import {createKoaHttpMiddleware, createExpressHttpMiddleware, createHttpClient} from "@push-rpc/http" +import { GBServer } from '../../src/app.js'; const app = new Koa(); +import {SocketServer} from "@push-rpc/core" +import * as koaBody from "koa-body" + + +export function createKoaHttpServer( + port: number, + getRemoteId: (ctx: Koa.Context) => string +): SocketServer { + const {onError, onConnection, middleware} = createKoaHttpMiddleware(getRemoteId) + + const app = new Koa() + app.use(koaBody.koaBody({multipart: true})) + app.use(middleware) + const server = app.listen(port) + + return { + onError, + onConnection, + close(cb) { + server.close(cb) + }, + } +} + + + + /** * Package for core.gbapp. @@ -60,9 +87,49 @@ export class GBBasicPackage implements IGBPackage { public async loadPackage (core: IGBCoreService, sequelize: Sequelize): Promise { core.sequelize.addModels([GuaribasSchedule]); - //app.use(koaBody.koaBody({ multipart: true, })); - app.listen(1111); + const dk = new DialogKeywords(); + const wa = new WebAutomationServices(); + const sys = new SystemKeywords(); + const dbg = new DebuggerService(); + const img = new ImageProcessingServices(); + +// remote id is required for assigning separate HTTP requests to a single session +function getRemoteId(ctx: Koa.Context) { + return "1" // share a single session for now, real impl could use cookies or some other meaning for HTTP sessions +} + + + + + + GBServer.globals.server.dk = createRpcServer(dk, createKoaHttpServer(5555, getRemoteId),{ + listeners: { + unsubscribed(subscriptions: number): void {}, + subscribed(subscriptions: number): void {}, + disconnected(remoteId: string, connections: number): void { + console.log(`Client ${remoteId} disconnected`) + }, + connected(remoteId: string, connections: number): void { + console.log(`New client ${remoteId} connected`) + }, + messageIn(...params): void { + console.log("IN ", params) + }, + messageOut(...params): void { + console.log("OUT ", params) + }, + }, + pingSendTimeout: null, + keepAliveTimeout: null, + }) + + console.log("RPC Server started at ws://localhost:5555") + + // GBServer.globals.wa = createRpcServer(wa, createWebsocketServer({port: 1112})); + // GBServer.globals.sys = createRpcServer(sys, createWebsocketServer({port: 1113})); + // GBServer.globals.dbg = createRpcServer(dbg, createWebsocketServer({port: 1114})); + // GBServer.globals.img = createRpcServer(img, createWebsocketServer({port: 1115})); } public async getDialogs (min: GBMinInstance) { @@ -81,28 +148,16 @@ export class GBBasicPackage implements IGBPackage { GBLog.verbose(`onExchangeData called.`); } public async loadBot (min: GBMinInstance): Promise { - const dk = new DialogKeywords(min, null, null); - const wa = new WebAutomationServices(min, null, dk); - const sys = new SystemKeywords(min, null, dk, wa); - const dbg = new DebuggerService(min, null, dk); - const img = new ImageProcessingServices(min, null, dk); - dk.wa = wa; - wa.sys = sys; - const dialogRouter = createServerRouter(`/api/v2/${min.botId}/dialog`, dk); - const waRouter = createServerRouter(`/api/v2/${min.botId}/webautomation`, wa); - const sysRouter = createServerRouter(`/api/v2/${min.botId}/system`, sys); - const dbgRouter = createServerRouter(`/api/v2/${min.botId}/debugger`, dbg); - const imgRouter = createServerRouter(`/api/v2/${min.botId}/imageprocessing`, dbg); - app.use(dialogRouter.routes()); - app.use(sysRouter.routes()); - app.use(waRouter.routes()); - app.use(dbgRouter.routes()); - app.use(imgRouter.routes()); - app.use(async (ctx, next) => { - if(ctx['status'] === 404){ - ctx.status = 404 - ctx.body = {msg:'emmmmmmm, seems 404'}; - } - }); + + const botId = min.botId; + GBServer.globals.debuggers[botId] = {}; + GBServer.globals.debuggers[botId].state = 0; + GBServer.globals.debuggers[botId].breaks = []; + GBServer.globals.debuggers[botId].stateInfo = 'Stopped'; + GBServer.globals.debuggers[botId].childProcess = null; + GBServer.globals.debuggers[botId].client = null; + GBServer.globals.debuggers[botId].conversationsMap = {}; + GBServer.globals.debuggers[botId].watermarkMap = {}; + } } diff --git a/packages/basic.gblib/services/DebuggerService.ts b/packages/basic.gblib/services/DebuggerService.ts index d036e95dc..2603d486e 100644 --- a/packages/basic.gblib/services/DebuggerService.ts +++ b/packages/basic.gblib/services/DebuggerService.ts @@ -44,44 +44,6 @@ import { spawn } from 'child_process'; * Web Automation services of conversation to be called by BASIC. */ export class DebuggerService { - /** - * Reference to minimal bot instance. - */ - public min: GBMinInstance; - - /** - * Reference to the base system keywords functions to be called. - */ - public dk: DialogKeywords; - - /** - * Current user object to get BASIC properties read. - */ - public user; - - /** - * HTML browser for conversation over page interaction. - */ - browser: any; - - sys: any; - - /** - * The number used in this execution for HEAR calls (useful for SET SCHEDULE). - */ - hrOn: string; - - userId: GuaribasUser; - debugWeb: boolean; - lastDebugWeb: Date; - - /** - * SYSTEM account maxLines,when used with impersonated contexts (eg. running in SET SCHEDULE). - */ - maxLines: number = 2000; - - conversationsMap = {}; - watermarkMap = {}; static systemVariables = [ 'AggregateError', 'Array', @@ -173,28 +135,6 @@ export class DebuggerService { 'valueOf' ]; - /** - * When creating this keyword facade,a bot instance is - * specified among the deployer service. - */ - constructor (min: GBMinInstance, user, dk) { - this.min = min; - this.user = user; - this.dk = dk; - - this.debugWeb = this.min.core.getParam(this.min.instance, 'Debug Web Automation', false); - - const botId = min.botId; - - GBServer.globals.debuggers[botId] = {}; - GBServer.globals.debuggers[botId].state = 0; - GBServer.globals.debuggers[botId].breaks = []; - GBServer.globals.debuggers[botId].stateInfo = 'Stopped'; - GBServer.globals.debuggers[botId].childProcess = null; - } - - private client; - public async breakpoint ({ botId, line }) { GBLog.info(`BASIC: Enabled breakpoint for ${botId} on ${line}.`); GBServer.globals.debuggers[botId].breaks.push(Number.parseInt(line)); @@ -239,14 +179,18 @@ export class DebuggerService { } public async context ({ botId }) { - const conversationId = this.conversationsMap[botId]; + const conversationsMap = GBServer.globals.debuggers[botId].conversationsMap; + const watermarkMap = GBServer.globals.debuggers[botId].watermarkMap; + + const conversationId = conversationsMap[botId]; let messages = []; - if (this.client) { - const response = await this.client.Conversations.Conversations_GetActivities({ + const client = GBServer.globals.debuggers[botId].client; + if (client) { + const response = await client.Conversations.Conversations_GetActivities({ conversationId: conversationId, - watermark: this.watermarkMap[botId] + watermark: watermarkMap[botId] }); - this.watermarkMap[botId] = response.obj.watermark; + watermarkMap[botId] = response.obj.watermark; let activities = response.obj.activites; if (activities && activities.length) { @@ -272,6 +216,8 @@ export class DebuggerService { } public async getRunning ({ botId, botApiKey, scriptName }) { + const conversationsMap = GBServer.globals.debuggers[botId].conversationsMap; + let error; botId = botId[0]; if (!GBServer.globals.debuggers[botId]) { @@ -296,20 +242,21 @@ export class DebuggerService { let min: GBMinInstance = GBServer.globals.minInstances.filter(p => p.instance.botId === botId)[0]; - this.client = await new Swagger({ + GBServer.globals.debuggers[botId].client = await new Swagger({ spec: JSON.parse(Fs.readFileSync('directline-3.0.json', 'utf8')), usePromise: true }); - this.client.clientAuthorizations.add( + const client = GBServer.globals.debuggers[botId].client; + client.clientAuthorizations.add( 'AuthorizationBotConnector', new Swagger.ApiKeyAuthorization('Authorization', `Bearer ${min.instance.webchatKey}`, 'header') ); - const response = await this.client.Conversations.Conversations_StartConversation(); + const response = await client.Conversations.Conversations_StartConversation(); const conversationId = response.obj.conversationId; - this.conversationsMap[botId] = conversationId; + conversationsMap[botId] = conversationId; GBServer.globals.debugConversationId = conversationId; - this.client.Conversations.Conversations_PostActivity({ + client.Conversations.Conversations_PostActivity({ conversationId: conversationId, activity: { textFormat: 'plain', diff --git a/packages/basic.gblib/services/DialogKeywords.ts b/packages/basic.gblib/services/DialogKeywords.ts index d1e677b41..47e89d3bb 100644 --- a/packages/basic.gblib/services/DialogKeywords.ts +++ b/packages/basic.gblib/services/DialogKeywords.ts @@ -69,67 +69,6 @@ const DEFAULT_HEAR_POLL_INTERVAL = 500; * Base services of conversation to be called by BASIC. */ export class DialogKeywords { - /** - * Reference to minimal bot instance. - */ - public min: GBMinInstance; - - /** - * Reference to the base system keywords functions to be called. - */ - public internalSys: SystemKeywords; - - /** - * Current user object to get BASIC properties read. - */ - public user; - - /** - * HTML browser for conversation over page interaction. - */ - browser: any; - - /** - * The number used in this execution for HEAR calls (useful for SET SCHEDULE). - */ - hrOn: string; - userId: GuaribasUser; - debugWeb: boolean; - lastDebugWeb: Date; - public wa; - - /** - * SYSTEM account maxLines,when used with impersonated contexts (eg. running in SET SCHEDULE). - */ - maxLines: number = 2000; - - public async getDeployer() { - return this.min.deployService; - } - - public async getMin() { - return this.min; - } - - /** - * When creating this keyword facade,a bot instance is - * specified among the deployer service. - */ - constructor(min: GBMinInstance, deployer: GBDeployer, user) { - this.min = min; - this.user = user; - this.internalSys = new SystemKeywords(min, deployer, this, null); - - this.debugWeb = this.min.core.getParam(this.min.instance, 'Debug Web Automation', false); - } - - /** - * Base reference of system keyword facade,called directly - * by the script. - */ - public sys(): SystemKeywords { - return this.internalSys; - } /** * @@ -144,7 +83,8 @@ export class DialogKeywords { * @param legends * @see https://www.npmjs.com/package/plot */ - public async chart({ type, data, legends, transpose }) { + public async chart({pid, type, data, legends, transpose }) { + const { min, user } = await DialogKeywords.getProcessInfo(pid); let table = [[]]; if (legends) { @@ -195,12 +135,12 @@ export class DialogKeywords { }; } - const gbaiName = `${this.min.botId}.gbai`; + const gbaiName = `${min.botId}.gbai`; const localName = Path.join('work', gbaiName, 'cache', `img${GBAdminService.getRndReadableIdentifier()}.jpg`); await ChartServices.screenshot(definition, localName); - const url = urlJoin(GBServer.globals.publicAddress, this.min.botId, 'cache', Path.basename(localName)); + const url = urlJoin(GBServer.globals.publicAddress, min.botId, 'cache', Path.basename(localName)); GBLog.info(`BASIC: Visualization: Chart generated at ${url}.`); @@ -228,7 +168,8 @@ export class DialogKeywords { * * @example x = TODAY */ - public async getToday({}) { + public async getToday({pid}) { + const { min, user } = await DialogKeywords.getProcessInfo(pid); let d = new Date(), month = '' + (d.getMonth() + 1), day = '' + d.getDate(), @@ -241,8 +182,8 @@ export class DialogKeywords { day = '0' + day; } - const contentLocale = this.min.core.getParam( - this.min.instance, + const contentLocale = min.core.getParam( + min.instance, 'Default Content Language', GBConfigService.get('DEFAULT_CONTENT_LANGUAGE') ); @@ -316,9 +257,10 @@ export class DialogKeywords { * @example day = WEEKDAY (date) * */ - public getWeekFromDate(pid, date) { - const contentLocale = this.min.core.getParam( - this.min.instance, + public async getWeekFromDate({pid, date}) { + const { min, user } = await DialogKeywords.getProcessInfo(pid); + const contentLocale = min.core.getParam( + min.instance, 'Default Content Language', GBConfigService.get('DEFAULT_CONTENT_LANGUAGE') ); @@ -465,7 +407,7 @@ export class DialogKeywords { return i; } - const contentLocale = this.min.core.getParam( + const contentLocale = min.core.getParam( min.instance, 'Default Content Language', GBConfigService.get('DEFAULT_CONTENT_LANGUAGE') @@ -488,9 +430,10 @@ export class DialogKeywords { * @example SAVE "contacts.xlsx", name, email, NOW * */ - public async getNow({}) { - const contentLocale = this.min.core.getParam( - this.min.instance, + public async getNow({pid}) { + const { min, user } = await DialogKeywords.getProcessInfo(pid); + const contentLocale = min.core.getParam( + min.instance, 'Default Content Language', GBConfigService.get('DEFAULT_CONTENT_LANGUAGE') ); @@ -574,9 +517,10 @@ export class DialogKeywords { * @example SET LANGUAGE "pt" * */ - public async setLanguage({ language }) { + public async setLanguage({ pid, language }) { + const { min, user } = await DialogKeywords.getProcessInfo(pid); const sec = new SecService(); - await sec.updateUserLocale(this.user.userId, language); + await sec.updateUserLocale(user.userId, language); } /** @@ -587,7 +531,7 @@ export class DialogKeywords { */ public async setIdGeneration({ mode }) { this['idGeneration'] = mode; - this['id'] = this.sys().getRandomId(); + this['id'] = new SystemKeywords().getRandomId(); } private isUserSystemParam(name: string): Boolean { @@ -751,8 +695,7 @@ export class DialogKeywords { * */ public async getHear({ pid, kind, arg }) { - const process = GBServer.globals.processes[pid]; - let { min, user } = await DialogKeywords.getProcessInfo(pid); + let { min, user, params } = await DialogKeywords.getProcessInfo(pid); // Handles first arg as an array of args. @@ -772,8 +715,8 @@ export class DialogKeywords { // containing the specified user other than the actual user // TODO: Store hrOn in processInfo. - if (this.hrOn) { - user = await sec.getUserFromAgentSystemId(this.hrOn); + if (params.hrOn) { + user = await sec.getUserFromAgentSystemId(params.hrOn); } const userId = user.userId; @@ -831,7 +774,7 @@ export class DialogKeywords { // Retrieves the .xlsx file associated with the HEAR var AS file.xlsx. - let { baseUrl, client } = await GBDeployer.internalGetDriveClient(this.min); + let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min); const botId = min.instance.botId; const path = urljoin(`${botId}.gbai`, `${botId}.gbdata`); let url = `${baseUrl}/drive/root:/${path}:/children`; @@ -884,13 +827,13 @@ export class DialogKeywords { // In case of unmatch, asks the person to try again. if (result === null) { - await this.getTalk({ pid, text: `Escolha por favor um dos itens sugeridos.` }); + await this.talk({ pid, text: `Escolha por favor um dos itens sugeridos.` }); return await this.getHear({ pid, kind, arg }); } } else if (kind === 'file') { GBLog.info(`BASIC (${min.botId}): Upload done for ${answer.filename}.`); - const handle = WebAutomationServices.cyrb53(this.min.botId + answer.filename); + const handle = WebAutomationServices.cyrb53(min.botId + answer.filename); GBServer.globals.files[handle] = answer; result = handle; } else if (kind === 'boolean') { @@ -907,7 +850,7 @@ export class DialogKeywords { const value = extractEntity(answer); if (value === null) { - await this.getTalk({ pid, text: 'Por favor, digite um e-mail válido.' }); + await this.talk({ pid, text: 'Por favor, digite um e-mail válido.' }); return await this.getHear({ pid, kind, arg }); } @@ -920,7 +863,7 @@ export class DialogKeywords { const value = extractEntity(answer); if (value === null || value.length != 1) { - await this.getTalk({ pid, text: 'Por favor, digite um nome válido.' }); + await this.talk({ pid, text: 'Por favor, digite um nome válido.' }); return await this.getHear({ pid, kind, arg }); } @@ -933,7 +876,7 @@ export class DialogKeywords { const value = extractEntity(answer); if (value === null || value.length != 1) { - await this.getTalk({ pid, text: 'Por favor, digite um número válido.' }); + await this.talk({ pid, text: 'Por favor, digite um número válido.' }); return await this.getHear({ pid, kind, arg }); } @@ -948,7 +891,7 @@ export class DialogKeywords { const value = extractEntity(answer); if (value === null || value.length != 1) { - await this.getTalk({ pid, text: 'Por favor, digite uma data no formato 12/12/2020.' }); + await this.talk({ pid, text: 'Por favor, digite uma data no formato 12/12/2020.' }); return await this.getHear({ pid, kind, arg }); } @@ -961,7 +904,7 @@ export class DialogKeywords { const value = extractEntity(answer); if (value === null || value.length != 1) { - await this.getTalk({ pid, text: 'Por favor, digite um horário no formato hh:ss.' }); + await this.talk({ pid, text: 'Por favor, digite um horário no formato hh:ss.' }); return await this.getHear({ pid, kind, arg }); } @@ -980,7 +923,7 @@ export class DialogKeywords { const value = extractEntity(answer); if (value === null || value.length != 1) { - await this.getTalk({ pid, text: 'Por favor, digite um valor monetário.' }); + await this.talk({ pid, text: 'Por favor, digite um valor monetário.' }); return await this.getHear({ pid, kind, arg }); } @@ -992,12 +935,12 @@ export class DialogKeywords { phoneNumber = phone(answer, { country: 'BRA' })[0]; phoneNumber = phoneUtil.parse(phoneNumber); } catch (error) { - await this.getTalk({ pid, text: Messages[locale].validation_enter_valid_mobile }); + await this.talk({ pid, text: Messages[locale].validation_enter_valid_mobile }); return await this.getHear({ pid, kind, arg }); } if (!phoneUtil.isPossibleNumber(phoneNumber)) { - await this.getTalk({ pid, text: 'Por favor, digite um número de telefone válido.' }); + await this.talk({ pid, text: 'Por favor, digite um número de telefone válido.' }); return await this.getHear({ pid, kind, arg }); } @@ -1017,7 +960,7 @@ export class DialogKeywords { const value = extractEntity(answer); if (value === null || value.length != 1) { - await this.getTalk({ pid, text: 'Por favor, digite um CEP válido.' }); + await this.talk({ pid, text: 'Por favor, digite um CEP válido.' }); return await this.getHear({ pid, kind, arg }); } @@ -1032,7 +975,7 @@ export class DialogKeywords { }); if (result === null) { - await this.getTalk({ pid, text: `Escolha por favor um dos itens sugeridos.` }); + await this.talk({ pid, text: `Escolha por favor um dos itens sugeridos.` }); return await this.getHear({ pid, kind, arg }); } } else if (kind === 'language') { @@ -1064,7 +1007,7 @@ export class DialogKeywords { }); if (result === null) { - await this.getTalk({ pid, text: `Escolha por favor um dos itens sugeridos.` }); + await this.talk({ pid, text: `Escolha por favor um dos itens sugeridos.` }); return await this.getHear({ pid, kind, arg }); } } @@ -1077,7 +1020,8 @@ export class DialogKeywords { /** * Prepares the next dialog to be shown to the specified user. */ - public async gotoDialog({ fromOrDialogName, dialogName }) { + public async gotoDialog({ pid, fromOrDialogName, dialogName }) { + const { min, user } = await DialogKeywords.getProcessInfo(pid); if (dialogName) { if (dialogName.charAt(0) === '/') { // https://github.com/GeneralBots/BotServer/issues/308 @@ -1087,7 +1031,7 @@ export class DialogKeywords { let user = await sec.getUserFromSystemId(fromOrDialogName); if (!user) { user = await sec.ensureUser( - this.min.instance.instanceId, + min.instance.instanceId, fromOrDialogName, fromOrDialogName, null, @@ -1121,7 +1065,7 @@ export class DialogKeywords { /** * Talks to the user by using the specified text. */ - public async getTalk({ pid, text }) { + public async talk({ pid, text }) { GBLog.info(`BASIC: TALK '${text}'.`); const { min, user } = await DialogKeywords.getProcessInfo(pid); diff --git a/packages/basic.gblib/services/GBVMService.ts b/packages/basic.gblib/services/GBVMService.ts index 334e98982..04cb7a819 100644 --- a/packages/basic.gblib/services/GBVMService.ts +++ b/packages/basic.gblib/services/GBVMService.ts @@ -51,6 +51,7 @@ import { DialogKeywords } from './DialogKeywords.js'; import { KeywordsExpressions } from './KeywordsExpressions.js'; import { GBLogEx } from '../../core.gbapp/services/GBLogEx.js'; import { GuaribasUser } from '../../security.gbapp/models/index.js'; +import { SystemKeywords } from './SystemKeywords.js'; /** * @fileoverview Decision was to priorize security(isolation) and debugging, @@ -340,7 +341,6 @@ export class GBVMService extends GBService { // Creates a class DialogKeywords which is the *this* pointer // in BASIC. - const dk = new DialogKeywords(min, deployer, null); const sandbox = {}; const contentLocale = min.core.getParam( min.instance, @@ -373,9 +373,11 @@ export class GBVMService extends GBService { userId: user.userId, instanceId: min.instance.instanceId }; + const dk = new DialogKeywords(); + const sys = new SystemKeywords(); sandbox['variables'] = variables; - sandbox['id'] = dk.sys().getRandomId(); + sandbox['id'] = sys.getRandomId(); sandbox['username'] = await dk.userName({ pid }); sandbox['mobile'] = await dk.userMobile({ pid }); sandbox['from'] = await dk.userMobile({ pid }); @@ -398,7 +400,7 @@ export class GBVMService extends GBService { console: 'inherit', wrapper: 'commonjs', require: { - builtin: ['stream', 'http', 'https', 'url', 'zlib'], + builtin: ['stream', 'http', 'https', 'url', 'zlib', 'net', 'tls', 'crypto', ], root: ['./'], external: true, context: 'sandbox' diff --git a/packages/basic.gblib/services/ImageProcessingServices.ts b/packages/basic.gblib/services/ImageProcessingServices.ts index 9bd96ba0f..b9df645c2 100644 --- a/packages/basic.gblib/services/ImageProcessingServices.ts +++ b/packages/basic.gblib/services/ImageProcessingServices.ts @@ -46,33 +46,6 @@ import { GBServer } from '../../../src/app.js'; * Image processing services of conversation to be called by BASIC. */ export class ImageProcessingServices { - /** - * Reference to minimal bot instance. - */ - public min: GBMinInstance; - - /** - * Reference to the base system keywords functions to be called. - */ - public dk: DialogKeywords; - - /** - * Current user object to get BASIC properties read. - */ - public user; - - sys: any; - - /** - * When creating this keyword facade,a bot instance is - * specified among the deployer service. - */ - constructor(min: GBMinInstance, user, dk) { - this.min = min; - this.user = user; - this.dk = dk; - } - /** * Sharpen the image. * @@ -116,7 +89,7 @@ export class ImageProcessingServices { paths.push(gbfile.path); }); - const botId = this.min.instance.botId; + const botId = min.instance.botId; const gbaiName = `${botId}.gbai`; const img = await joinImages(paths); const localName = Path.join('work', gbaiName, 'cache', `img-mrg${GBAdminService.getRndReadableIdentifier()}.png`); diff --git a/packages/basic.gblib/services/SystemKeywords.ts b/packages/basic.gblib/services/SystemKeywords.ts index b76925622..c62f34cb4 100644 --- a/packages/basic.gblib/services/SystemKeywords.ts +++ b/packages/basic.gblib/services/SystemKeywords.ts @@ -57,6 +57,7 @@ import ImageModule from 'open-docxtemplater-image-module'; import DynamicsWebApi from 'dynamics-web-api'; import * as MSAL from '@azure/msal-node'; import { GBConversationalService } from '../../core.gbapp/services/GBConversationalService.js'; +import { WebAutomationServices } from './WebAutomationServices.js'; /** @@ -67,23 +68,6 @@ import { GBConversationalService } from '../../core.gbapp/services/GBConversatio * BASIC system class for extra manipulation of bot behaviour. */ export class SystemKeywords { - /** - * Reference to minimal bot instance. - */ - public min: GBMinInstance; - - dk: DialogKeywords; - wa; - - /** - * When creating this keyword facade, a bot instance is - * specified among the deployer service. - */ - constructor(min: GBMinInstance, deployer: GBDeployer, dk: DialogKeywords, wa) { - this.min = min; - this.wa = wa; - this.dk = dk; - } public async callVM({ pid, text }) { const { min, user } = await DialogKeywords.getProcessInfo(pid); @@ -114,7 +98,7 @@ export class SystemKeywords { let caption = (await computerVisionClient.describeImage(url)).captions[0]; - const contentLocale = this.min.core.getParam( + const contentLocale = min.core.getParam( min.instance, 'Default Content Language', GBConfigService.get('DEFAULT_CONTENT_LANGUAGE') @@ -158,7 +142,7 @@ export class SystemKeywords { public async sortBy({ pid, array, memberName }) { const { min, user } = await DialogKeywords.getProcessInfo(pid); memberName = memberName.trim(); - const contentLocale = this.min.core.getParam( + const contentLocale = min.core.getParam( min.instance, 'Default Content Language', GBConfigService.get('DEFAULT_CONTENT_LANGUAGE') @@ -257,7 +241,7 @@ export class SystemKeywords { // Includes the associated CSS related to current theme. - const theme = this.dk.user.basicOptions.theme; + const theme: string = 'white'; // TODO: params.theme; switch (theme) { case 'white': await page.addStyleTag({ path: 'node_modules/tabulator-tables/dist/css/tabulator_simple.min.css' }); @@ -342,9 +326,9 @@ export class SystemKeywords { if (data.data) { const gbfile = data.data; - let { baseUrl, client } = await GBDeployer.internalGetDriveClient(this.min); - const botId = this.min.instance.botId; - const gbaiName = `${this.min.botId}.gbai`; + let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min); + const botId = min.instance.botId; + const gbaiName = `${min.botId}.gbai`; const tmpDocx = urlJoin(gbaiName, `${botId}.gbdrive`, `tmp${GBAdminService.getRndReadableIdentifier()}.docx`); // Performs the conversion operation. @@ -425,7 +409,7 @@ export class SystemKeywords { * Retrives a random id with a length of five, every time it is called. */ public getRandomId() { - const idGeneration = this.dk['idGeneration']; + const idGeneration = '1v'; // TODO: this.dk['idGeneration']; if (idGeneration && idGeneration.trim().toLowerCase() === 'number') { return GBAdminService.getNumberIdentifier(); } else { @@ -488,15 +472,15 @@ export class SystemKeywords { * @example SET page, "elementHTMLSelector", "text" * */ - public async set({ pid, file, address, value }): Promise { + public async set({ pid, handle, file, address, value }): Promise { const { min, user } = await DialogKeywords.getProcessInfo(pid); // Handles calls for HTML stuff if (file._javascriptEnabled) { - const page = file; - GBLog.info(`BASIC: Web automation setting ${page}' to '${value}' (SET). `); - await this.wa.setElementText({ page, selector: address, text: value }); + + GBLog.info(`BASIC: Web automation setting ${file}' to '${value}' (SET). `); + await new WebAutomationServices().setElementText({ pid, handle, selector: address, text: value }); return; } @@ -550,9 +534,10 @@ export class SystemKeywords { * */ public async saveFile({ pid, file, data }): Promise { + const { min, user } = await DialogKeywords.getProcessInfo(pid); GBLog.info(`BASIC: Saving '${file}' (SAVE file).`); - let { baseUrl, client } = await GBDeployer.internalGetDriveClient(this.min); - const botId = this.min.instance.botId; + let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min); + const botId = min.instance.botId; const path = `/${botId}.gbai/${botId}.gbdrive`; // Checks if it is a GB FILE object. @@ -581,11 +566,12 @@ export class SystemKeywords { * */ public async save({ pid, args }): Promise { + const { min, user } = await DialogKeywords.getProcessInfo(pid); const file = args[0]; args.shift(); GBLog.info(`BASIC: Saving '${file}' (SAVE). Args: ${args.join(',')}.`); - let { baseUrl, client } = await GBDeployer.internalGetDriveClient(this.min); - const botId = this.min.instance.botId; + let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min); + const botId = min.instance.botId; const path = `/${botId}.gbai/${botId}.gbdata`; let document = await this.internalGetDocument(client, baseUrl, path, file); @@ -626,6 +612,7 @@ export class SystemKeywords { * */ public async get({ pid, file, addressOrHeaders, httpUsername, httpPs, qs, streaming }): Promise { + const { min, user } = await DialogKeywords.getProcessInfo(pid); if (file.startsWith('http')) { return await this.getByHttp({ pid, @@ -637,8 +624,8 @@ export class SystemKeywords { }); } else { GBLog.info(`BASIC: GET '${addressOrHeaders}' in '${file}'.`); - let { baseUrl, client } = await GBDeployer.internalGetDriveClient(this.min); - const botId = this.min.instance.botId; + let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min); + const botId = min.instance.botId; const path = `/${botId}.gbai/${botId}.gbdata`; let document = await this.internalGetDocument(client, baseUrl, path, file); @@ -659,9 +646,10 @@ export class SystemKeywords { } } - public isValidDate({ pid, dt }) { - const contentLocale = this.min.core.getParam( - this.min.instance, + public async isValidDate({ pid, dt }) { + const { min, user } = await DialogKeywords.getProcessInfo(pid); + const contentLocale = min.core.getParam( + min.instance, 'Default Content Language', GBConfigService.get('DEFAULT_CONTENT_LANGUAGE') ); @@ -678,7 +666,8 @@ export class SystemKeywords { return !isNaN(date.valueOf()); } - public isValidNumber({ pid, number }) { + public async isValidNumber({ pid, number }) { + const { min, user } = await DialogKeywords.getProcessInfo(pid); if (number === '') { return false; } @@ -704,21 +693,22 @@ export class SystemKeywords { * */ public async getFind({ pid, args }): Promise { + const { min, user, params } = await DialogKeywords.getProcessInfo(pid); const file = args[0]; args.shift(); - const botId = this.min.instance.botId; + const botId = min.instance.botId; const path = `/${botId}.gbai/${botId}.gbdata`; // MAX LINES property. let maxLines; - if (this.dk.user && this.dk.user.basicOptions && this.dk.user.basicOptions.maxLines) { - if (this.dk.user.basicOptions.maxLines.toString().toLowerCase() !== 'default') { - maxLines = Number.parseInt(this.dk.user.basicOptions.maxLines).valueOf(); + if (user && params && params.maxLines) { + if (params.maxLines.toString().toLowerCase() !== 'default') { + maxLines = Number.parseInt(params.maxLines).valueOf(); } } else { - maxLines = this.dk.maxLines; + maxLines = maxLines; } GBLog.info(`BASIC: FIND running on ${file} (maxLines: ${maxLines}) and args: ${JSON.stringify(args)}...`); @@ -760,7 +750,7 @@ export class SystemKeywords { rows[i] = result[i]; } } else if (file['cTag']) { - const gbaiName = `${this.min.botId}.gbai`; + const gbaiName = `${min.botId}.gbai`; const localName = Path.join('work', gbaiName, 'cache', `csv${GBAdminService.getRndReadableIdentifier()}.csv`); const url = file['@microsoft.graph.downloadUrl']; const response = await fetch(url); @@ -785,7 +775,7 @@ export class SystemKeywords { } } } else { - let { baseUrl, client } = await GBDeployer.internalGetDriveClient(this.min); + let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min); let document; document = await this.internalGetDocument(client, baseUrl, path, file); @@ -834,8 +824,8 @@ export class SystemKeywords { return filter; }; - const contentLocale = this.min.core.getParam( - this.min.instance, + const contentLocale = min.core.getParam( + min.instance, 'Default Content Language', GBConfigService.get('DEFAULT_CONTENT_LANGUAGE') ); @@ -888,8 +878,8 @@ export class SystemKeywords { await CollectionUtil.asyncForEach(filters, async filter => { let result = rows[foundIndex][filter.columnIndex]; let wholeWord = true; - if (this.dk.user && this.dk.user.basicOptions && this.dk.user.basicOptions.wholeWord) { - wholeWord = this.dk.user.basicOptions.wholeWord; + if (user && params && params.wholeWord) { + wholeWord = params.wholeWord; } switch (filter.dataType) { @@ -1101,8 +1091,9 @@ export class SystemKeywords { * */ public async createFolder({ pid, name }) { - let { baseUrl, client } = await GBDeployer.internalGetDriveClient(this.min); - const botId = this.min.instance.botId; + const { min, user, params } = await DialogKeywords.getProcessInfo(pid); + let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min); + const botId = min.instance.botId; let path = `/${botId}.gbai/${botId}.gbdrive`; // Extracts each part of path to call create folder to each @@ -1150,8 +1141,9 @@ export class SystemKeywords { * */ public async shareFolder({ pid, folder, email, message }) { - let { baseUrl, client } = await GBDeployer.internalGetDriveClient(this.min); - const root = urlJoin(`/${this.min.botId}.gbai/${this.min.botId}.gbdrive`, folder); + const { min, user, params } = await DialogKeywords.getProcessInfo(pid); + let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min); + const root = urlJoin(`/${min.botId}.gbai/${min.botId}.gbdrive`, folder); const src = await client.api(`${baseUrl}/drive/root:/${root}`).get(); @@ -1177,9 +1169,10 @@ export class SystemKeywords { * */ public async copyFile({ pid, src, dest }) { + const { min, user, params } = await DialogKeywords.getProcessInfo(pid); GBLog.info(`BASIC: BEGINING COPY '${src}' to '${dest}'`); - let { baseUrl, client } = await GBDeployer.internalGetDriveClient(this.min); - const botId = this.min.instance.botId; + let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min); + const botId = min.instance.botId; // Normalizes all slashes. @@ -1237,9 +1230,10 @@ export class SystemKeywords { * */ public async convert({ pid, src, dest }) { + const { min, user, params } = await DialogKeywords.getProcessInfo(pid); GBLog.info(`BASIC: CONVERT '${src}' to '${dest}'`); - let { baseUrl, client } = await GBDeployer.internalGetDriveClient(this.min); - const botId = this.min.instance.botId; + let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min); + const botId = min.instance.botId; // Normalizes all slashes. @@ -1339,7 +1333,7 @@ export class SystemKeywords { * @example * * user = put "http://server/path", "data" - * talk "The updated user area is" + user.area + * talk "The updated user area is" + area * */ public async putByHttp({ pid, url, data, headers }) { @@ -1359,7 +1353,7 @@ export class SystemKeywords { * @example * * user = post "http://server/path", "data" - * talk "The updated user area is" + user.area + * talk "The updated user area is" + area * */ public async postByHttp({ pid, url, data, headers }) { @@ -1435,13 +1429,13 @@ export class SystemKeywords { */ public async fill({ pid, templateName, data }) { const { min, user } = await DialogKeywords.getProcessInfo(pid); - const botId = this.min.instance.botId; + const botId = min.instance.botId; const gbaiName = `${botId}.gbai`; let localName; // Downloads template from .gbdrive. - let { baseUrl, client } = await GBDeployer.internalGetDriveClient(this.min); + let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min); let path = '/' + urlJoin(gbaiName, `${botId}.gbdrive`); let template = await this.internalGetDocument(client, baseUrl, path, templateName); let url = template['@microsoft.graph.downloadUrl']; @@ -1473,7 +1467,7 @@ export class SystemKeywords { for (const kind of ['png', 'jpg', 'jpeg']) { if (value.endsWith && value.endsWith(`.${kind}`)) { - const { baseUrl, client } = await GBDeployer.internalGetDriveClient(this.min); + const { baseUrl, client } = await GBDeployer.internalGetDriveClient(min); path = urlJoin(gbaiName, `${botId}.gbdrive`); if (value.indexOf('/') !== -1) { @@ -1596,16 +1590,16 @@ export class SystemKeywords { public async merge({ pid, file, data, key1, key2 }): Promise { GBLog.info(`BASIC: MERGE running on ${file} and key1: ${key1}, key2: ${key2}...`); - const { min, user } = await DialogKeywords.getProcessInfo(pid); + const { min, user, params } = await DialogKeywords.getProcessInfo(pid); const botId = min.instance.botId; const path = `/${botId}.gbai/${botId}.gbdata`; // MAX LINES property. let maxLines = 1000; - if (this.dk.user && this.dk.user.basicOptions && this.dk.user.basicOptions.maxLines) { - if (this.dk.user.basicOptions.maxLines.toString().toLowerCase() !== 'default') { - maxLines = Number.parseInt(this.dk.user.basicOptions.maxLines).valueOf(); + if (user && params && params.maxLines) { + if (params.maxLines.toString().toLowerCase() !== 'default') { + maxLines = Number.parseInt(params.maxLines).valueOf(); } } @@ -1694,7 +1688,7 @@ export class SystemKeywords { const address = `${cell}:${cell}`; if (value !== found[columnName]) { - await this.set({ pid, file, address, value }); + await this.set({ pid, handle:null, file, address, value }); merges++; } } diff --git a/packages/basic.gblib/services/WebAutomationServices.ts b/packages/basic.gblib/services/WebAutomationServices.ts index c9aa7b66b..d011cf003 100644 --- a/packages/basic.gblib/services/WebAutomationServices.ts +++ b/packages/basic.gblib/services/WebAutomationServices.ts @@ -46,39 +46,18 @@ import { DialogKeywords } from './DialogKeywords.js'; import { GBDeployer } from '../../core.gbapp/services/GBDeployer.js'; import { Mutex } from 'async-mutex'; import { GBLogEx } from '../../core.gbapp/services/GBLogEx.js'; +import { SystemKeywords } from './SystemKeywords.js'; /** * Web Automation services of conversation to be called by BASIC. */ export class WebAutomationServices { - /** - * Reference to minimal bot instance. - */ - public min: GBMinInstance; - - /** - * Reference to the base system keywords functions to be called. - */ - public dk: DialogKeywords; - - /** - * Current user object to get BASIC properties read. - */ - public user; - - /** - * HTML browser for conversation over page interaction. - */ - browser: any; - - sys: any; /** * The number used in this execution for HEAR calls (useful for SET SCHEDULE). */ hrOn: string; - userId: GuaribasUser; debugWeb: boolean; lastDebugWeb: Date; @@ -102,17 +81,6 @@ export class WebAutomationServices { return 4294967296 * (2097151 & h2) + (h1 >>> 0); }; - /** - * When creating this keyword facade,a bot instance is - * specified among the deployer service. - */ - constructor(min: GBMinInstance, user, dk) { - this.min = min; - this.user = user; - this.dk = dk; - - this.debugWeb = this.min.core.getParam(this.min.instance, 'Debug Web Automation', false); - } public async getCloseHandles({ pid }) { const { min, user } = await DialogKeywords.getProcessInfo(pid); @@ -185,7 +153,7 @@ export class WebAutomationServices { // A new web session is being created. - handle = WebAutomationServices.cyrb53(this.min.botId + url); + handle = WebAutomationServices.cyrb53(min.botId + url); GBServer.globals.webSessions[handle] = session = {}; session.sessionName = sessionName; @@ -294,16 +262,17 @@ export class WebAutomationServices { } private async debugStepWeb(pid, page) { + const { min, user } = await DialogKeywords.getProcessInfo(pid); let refresh = true; if (this.lastDebugWeb) { refresh = new Date().getTime() - this.lastDebugWeb.getTime() > 5000; } if (this.debugWeb && refresh) { - const mobile = this.min.core.getParam(this.min.instance, 'Bot Admin Number', null); + const mobile = min.core.getParam(min.instance, 'Bot Admin Number', null); const filename = page; if (mobile) { - await this.dk.sendFileTo({ pid: pid, mobile, filename, caption: 'General Bots Debugger' }); + await new DialogKeywords().sendFileTo({ pid: pid, mobile, filename, caption: 'General Bots Debugger' }); } this.lastDebugWeb = new Date(); } @@ -346,16 +315,17 @@ export class WebAutomationServices { * * @example file = SCREENSHOT page */ - public async screenshot({ handle, selector }) { + public async screenshot({pid, handle, selector }) { + const { min, user } = await DialogKeywords.getProcessInfo(pid); const page = this.getPageByHandle(handle); GBLog.info(`BASIC: Web Automation SCREENSHOT ${selector}.`); - const gbaiName = `${this.min.botId}.gbai`; + const gbaiName = `${min.botId}.gbai`; const localName = Path.join('work', gbaiName, 'cache', `screen-${GBAdminService.getRndReadableIdentifier()}.jpg`); await page.screenshot({ path: localName }); - const url = urlJoin(GBServer.globals.publicAddress, this.min.botId, 'cache', Path.basename(localName)); + const url = urlJoin(GBServer.globals.publicAddress, min.botId, 'cache', Path.basename(localName)); GBLog.info(`BASIC: WebAutomation: Screenshot captured at ${url}.`); return url; @@ -381,7 +351,8 @@ export class WebAutomationServices { * * @example file = DOWNLOAD element, folder */ - public async download({ handle, selector, folder }) { + public async download({ pid, handle, selector, folder }) { + const { min, user } = await DialogKeywords.getProcessInfo(pid); const page = this.getPageByHandle(handle); const element = await this.getBySelector({ handle, selector }); @@ -429,8 +400,8 @@ export class WebAutomationServices { const res = await fetch(options.uri, options); result = Buffer.from(await res.arrayBuffer()); } - let { baseUrl, client } = await GBDeployer.internalGetDriveClient(this.min); - const botId = this.min.instance.botId; + let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min); + const botId = min.instance.botId; // Normalizes all slashes. @@ -444,7 +415,7 @@ export class WebAutomationServices { // Checks if the destination contains subfolders that // need to be created. - folder = await this.sys.createFolder(folder); + folder = await new SystemKeywords().createFolder(folder); // Performs the conversion operation getting a reference // to the source and calling /content on drive API. diff --git a/packages/basic.gblib/services/vm2-process/vm2ProcessRunner.ts b/packages/basic.gblib/services/vm2-process/vm2ProcessRunner.ts index f1cbc3c87..adb145262 100644 --- a/packages/basic.gblib/services/vm2-process/vm2ProcessRunner.ts +++ b/packages/basic.gblib/services/vm2-process/vm2ProcessRunner.ts @@ -9,7 +9,7 @@ const evaluate = async (script, scope) => { console: 'inherit', wrapper: 'none', require: { - builtin: ['stream', 'http', 'https', 'url', 'buffer', 'zlib', 'isomorphic-fetch', 'punycode', 'encoding'], + builtin: ['stream', 'http', 'https', 'url', 'buffer', 'zlib', 'isomorphic-fetch', 'punycode', 'encoding', 'net'], root: ['./'], external: true, context: 'sandbox' diff --git a/packages/core.gbapp/services/GBMinService.ts b/packages/core.gbapp/services/GBMinService.ts index 8a014a909..304a20781 100644 --- a/packages/core.gbapp/services/GBMinService.ts +++ b/packages/core.gbapp/services/GBMinService.ts @@ -911,7 +911,7 @@ export class GBMinService { ); const botToken = await credentials.getToken(); const headers = { Authorization: `Bearer ${botToken}` }; - const t = new SystemKeywords(null, null, null, null); + const t = new SystemKeywords( ); const data = await t.getByHttp({ pid: 0, url: file.contentUrl, diff --git a/src/RootData.ts b/src/RootData.ts index abecfa3b7..f07375796 100644 --- a/src/RootData.ts +++ b/src/RootData.ts @@ -42,7 +42,7 @@ import { GBMinService } from '../packages/core.gbapp/services/GBMinService.js'; */ export class RootData { - public webSessions: {} // List of Web Automation sessions. + public webSessions: {}; // List of Web Automation sessions. public processes: {}; // List of .gbdialog active executions. public files: {}; // List of uploaded files handled. public publicAddress: string; // URI for BotServer. @@ -59,5 +59,10 @@ export class RootData { public debugConversationId: any; // Used to self-message during debug. public debuggers: any[]; // Client of attached Debugger instances by botId. public chatGPT: any; // ChatGPT API handle (shared Browser). + public dk; + public wa; + public sys; + public dbg; + public img; indexSemaphore: any; }