diff --git a/packages/basic.gblib/services/DebuggerService.ts b/packages/basic.gblib/services/DebuggerService.ts index a34b6fdf..f3bc6c01 100644 --- a/packages/basic.gblib/services/DebuggerService.ts +++ b/packages/basic.gblib/services/DebuggerService.ts @@ -32,7 +32,7 @@ 'use strict'; -import { GBLog, GBMinInstance } from 'botlib'; +import { GBError, GBLog, GBMinInstance } from 'botlib'; import { GBServer } from '../../../src/app'; import { GBAdminService } from '../../admin.gbapp/services/GBAdminService'; import { GuaribasUser } from '../../security.gbapp/models'; @@ -52,10 +52,9 @@ const url = require('url'); * Web Automation services of conversation to be called by BASIC. */ export class DebuggerService { - /** - * Reference to minimal bot instance. - */ + * Reference to minimal bot instance. + */ public min: GBMinInstance; /** @@ -91,6 +90,96 @@ export class DebuggerService { conversationsMap = {}; watermarkMap = {}; + static systemVariables = [ + 'AggregateError', + 'Array', + 'ArrayBuffer', + 'Atomics', + 'BigInt', + 'BigInt64Array', + 'BigUint64Array', + 'Boolean', + 'DataView', + 'Date', + 'Error', + 'EvalError', + 'FinalizationRegistry', + 'Float32Array', + 'Float64Array', + 'Function', + 'Headers', + 'Infinity', + 'Int16Array', + 'Int32Array', + 'Int8Array', + 'Intl', + 'JSON', + 'Map', + 'Math', + 'NaN', + 'Number', + 'Object', + 'Promise', + 'Proxy', + 'RangeError', + 'ReferenceError', + 'Reflect', + 'RegExp', + 'Request', + 'Response', + 'Set', + 'SharedArrayBuffer', + 'String', + 'Symbol', + 'SyntaxError', + 'TypeError', + 'URIError', + 'Uint16Array', + 'Uint32Array', + 'Uint8Array', + 'Uint8ClampedArray', + 'VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL', + 'WeakMap', + 'WeakRef', + 'WeakSet', + 'WebAssembly', + '__defineGetter__', + '__defineSetter__', + '__lookupGetter__', + '__lookupSetter__', + '__proto__', + 'clearImmediate', + 'clearInterval', + 'clearTimeout', + 'console', + 'constructor', + 'decodeURI', + 'decodeURIComponent', + 'dss', + 'encodeURI', + 'encodeURIComponent', + 'escape', + 'eval', + 'fetch', + 'global', + 'globalThis', + 'hasOwnProperty', + 'isFinite', + 'isNaN', + 'isPrototypeOf', + 'parseFloat', + 'parseInt', + 'process', + 'propertyIsEnumerable', + 'setImmediate', + 'setInterval', + 'setTimeout', + 'toLocaleString', + 'toString', + 'undefined', + 'unescape', + 'valueOf' + ]; /** * When creating this keyword facade,a bot instance is @@ -101,110 +190,111 @@ export class DebuggerService { this.user = user; this.dk = dk; - this.debugWeb = this.min.core.getParam( - this.min.instance, - 'Debug Web Automation', - false - ); + 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 = 1; + GBServer.globals.debuggers[botId].state = 0; GBServer.globals.debuggers[botId].breaks = []; - } private client; public async breakpoint({ botId, botApiKey, line }) { - + GBLog.info(`GBDEBUG: Enabled breakpoint for ${botId} on ${line}.`); GBServer.globals.debuggers[botId].breaks.push(Number.parseInt(line)); - } - public async removeBreakPoint({ botId, botApiKey, line }) { + public async resume({ botId, botApiKey, force }) { const client = GBServer.globals.debuggers[botId].client; - - - } - - public async continueRun({ botId, botApiKey, force }) { - const client = GBServer.globals.debuggers[botId].client; - client.Debugger.resume(); + await client.Debugger.resume(); } public async stop({ botId, botApiKey, force }) { + GBServer.globals.debuggers[botId].state = 0; const client = GBServer.globals.debuggers[botId].client; - client.close(); + await client.close(); } - public async stepOver({ botId, botApiKey }) { - const client = GBServer.globals.debuggers[botId].client; - client.stepOver(); + public async step({ botId, botApiKey }) { + if (GBServer.globals.debuggers[botId].state === 2) { + const client = GBServer.globals.debuggers[botId].client; + await client.stepOver(); + } else { + throw new GBError(new Error('Invalid call to stepOver and state not being debug(2).')); + } } - public async getExecutionContext({ botId, botApiKey, force }) { - - const client = GBServer.globals.debuggers[botId].client; + public async context({ botId, botApiKey, force }) { const conversationId = this.conversationsMap[botId]; - - - const response = await client.Conversations.Conversations_GetActivities({ - conversationId: conversationId, - watermark: this.watermarkMap[botId] - }); - this.watermarkMap[botId] = response.obj.watermark; - let activities = response.obj.activites; let messages = []; - if (activities && activities.length) { - activities = activities.filter(m => m.from.id === botId && m.type === 'message'); - if (activities.length) { - activities.forEach(activity => { - messages.push({ text: activity.text }); - GBLog.info(`GBDEBUG: SND TO WORD ${activity.text}`); - }); + if (this.client) { + const response = await this.client.Conversations.Conversations_GetActivities({ + conversationId: conversationId, + watermark: this.watermarkMap[botId] + }); + this.watermarkMap[botId] = response.obj.watermark; + let activities = response.obj.activites; + + if (activities && activities.length) { + activities = activities.filter(m => m.from.id === botId && m.type === 'message'); + if (activities.length) { + activities.forEach(activity => { + messages.push({ text: activity.text }); + GBLog.info(`Debugger sending text to API: ${activity.text}`); + }); + } } } - return { state: GBServer.globals.debuggers[botId].state, messages, scope: GBServer.globals.debuggers[botId].scope }; + let messagesText = messages.join('\n'); + + return { + state: GBServer.globals.debuggers[botId].state, + messagesText, + scope: GBServer.globals.debuggers[botId].scope + }; } - public async run({ botId, botApiKey, scriptName }) { - - GBLog.info(`BASIC: Running ${botId} in DEBUG mode.`); + public async debug({ botId, botApiKey, scriptName }) { + if (GBServer.globals.debuggers[botId].state === 1) { + throw new Error(`Cannot DEBUG an already running process. ${botId}`); + } else if (GBServer.globals.debuggers[botId].state === 2) { + GBLog.info(`BASIC: Releasing execution ${botId} in DEBUG mode.`); + return await this.continueRun({ botId, botApiKey, force: false }); + } else { + GBLog.info(`BASIC: Running ${botId} in DEBUG mode.`); + GBServer.globals.debuggers[botId].state = 1; - GBServer.globals.debuggers[botId].state = 1; + let min: GBMinInstance = GBServer.globals.minInstances.filter(p => p.instance.botId === botId)[0]; + this.client = await new Swagger({ + spec: JSON.parse(fs.readFileSync('directline-3.0.json', 'utf8')), + usePromise: true + }); + this.client.clientAuthorizations.add( + 'AuthorizationBotConnector', + new Swagger.ApiKeyAuthorization('Authorization', `Bearer ${min.instance.webchatKey}`, 'header') + ); + const response = await this.client.Conversations.Conversations_StartConversation(); + const conversationId = response.obj.conversationId; + this.conversationsMap[botId] = conversationId; + GBServer.globals.debugConversationId = conversationId; - let min: GBMinInstance = GBServer.globals.minInstances.filter( - p => p.instance.botId === botId - )[0]; - - this.client = await new Swagger({ - spec: JSON.parse(fs.readFileSync('directline-3.0.json', 'utf8')), usePromise: true - }); - this.client.clientAuthorizations.add( - 'AuthorizationBotConnector', - new Swagger.ApiKeyAuthorization('Authorization', `Bearer ${min.instance.webchatKey}`, 'header') - ); - const response = await this.client.Conversations.Conversations_StartConversation(); - const conversationId = response.obj.conversationId; - this.conversationsMap[botId] = conversationId; - GBServer.globals.debugConversationId = conversationId; - - this.client.Conversations.Conversations_PostActivity({ - conversationId: conversationId, - activity: { - textFormat: 'plain', - text: `/call ${scriptName}`, - type: 'message', - from: { - id: 'test', - name: 'test' + this.client.Conversations.Conversations_PostActivity({ + conversationId: conversationId, + activity: { + textFormat: 'plain', + text: `/calldbg ${scriptName}`, + type: 'message', + from: { + id: 'test', + name: 'test' + } } - } - }); + }); + } } -} \ No newline at end of file +} diff --git a/packages/basic.gblib/services/GBVMService.ts b/packages/basic.gblib/services/GBVMService.ts index c88873ea..8f1263cb 100644 --- a/packages/basic.gblib/services/GBVMService.ts +++ b/packages/basic.gblib/services/GBVMService.ts @@ -765,7 +765,9 @@ export class GBVMService extends GBService { /** * Executes the converted JavaScript from BASIC code inside execution context. */ - public static async callVM(text: string, min: GBMinInstance, step, GBDialogdeployer: GBDeployer) { + public static async callVM(text: string, min: GBMinInstance, step, GBDialogdeployer: GBDeployer, debug: boolean) { + + const debuggerPort = 9222; // Creates a class DialogKeywords which is the *this* pointer // in BASIC. @@ -829,7 +831,8 @@ export class GBVMService extends GBService { const { run } = createVm2Pool({ min: 0, max: 0, - debuggerPort: 9222, + debug: debug, + debuggerPort: debuggerPort, botId: botId, cpu: 100, memory: 50000, diff --git a/packages/basic.gblib/services/ScheduleServices.ts b/packages/basic.gblib/services/ScheduleServices.ts index d3a7d1fb..10d9631b 100644 --- a/packages/basic.gblib/services/ScheduleServices.ts +++ b/packages/basic.gblib/services/ScheduleServices.ts @@ -148,7 +148,7 @@ export class ScheduleServices extends GBService { let min: GBMinInstance = GBServer.globals.minInstances.filter( p => p.instance.instanceId === item.instanceId )[0]; - await GBVMService.callVM(script, min, null, null); + await GBVMService.callVM(script, min, null, null, false); }; (async () => { await finalData(); diff --git a/packages/basic.gblib/services/SystemKeywords.ts b/packages/basic.gblib/services/SystemKeywords.ts index 80a23e37..d53b8200 100644 --- a/packages/basic.gblib/services/SystemKeywords.ts +++ b/packages/basic.gblib/services/SystemKeywords.ts @@ -99,7 +99,7 @@ export class SystemKeywords { const step = null; const deployer = null; - return await GBVMService.callVM(text, min, step, deployer); + return await GBVMService.callVM(text, min, step, deployer, false); } public async append({args}) { diff --git a/packages/basic.gblib/services/vm2-process/index.ts b/packages/basic.gblib/services/vm2-process/index.ts index b8101438..627abea8 100644 --- a/packages/basic.gblib/services/vm2-process/index.ts +++ b/packages/basic.gblib/services/vm2-process/index.ts @@ -6,6 +6,7 @@ const net = require('net'); import { GBLog } from 'botlib'; import { CollectionUtil } from 'pragmatismo-io-framework'; import { GBServer } from '../../../../src/app'; +import { DebuggerService } from '../DebuggerService'; const finalStream = require('final-stream'); const waitUntil = condition => { @@ -53,7 +54,7 @@ const createVm2Pool = ({ min, max, ...limits }) => { limits.cpu, '--', 'node', - `--inspect=${limits.debuggerPort}`, + `${limits.debug ? '--inspect=' + limits.debuggerPort : ''}`, `--experimental-fetch`, `--max-old-space-size=${limits.memory}`, limits.script, @@ -79,60 +80,71 @@ const createVm2Pool = ({ min, max, ...limits }) => { }); let socket = null; - await waitUntil(() => childProcess.socket); socket = net.createConnection(childProcess.socket); socket.write(JSON.stringify({ code, scope }) + '\n'); - // Only attach if called by /debugger/run. + // Only attach if called by debugger/run. if (GBServer.globals.debuggers[limits.botId]) { - - const debug = async () => { return new Promise((resolve, reject) => { CDP(async client => { const { Debugger, Runtime } = client; try { GBServer.globals.debuggers[limits.botId].client = client; - await client.Runtime.runIfWaitingForDebugger(); - await client.Debugger.enable(); - async function mainScript({ Debugger }) { - return new Promise((fulfill, reject) => { - Debugger.scriptParsed(params => { - const { scriptId, url } = params; - fulfill(scriptId); - }); - }); - } - - await CollectionUtil.asyncForEach(GBServer.globals.debuggers[limits.botId].breaks, async brk => { - const scriptId = await mainScript(client); - const { breakpointId } = await client.Debugger.setBreakpoint({ - location: { - scriptId, - lineNumber: brk - } - }); - GBLog.info(`BASIC break defined ${breakpointId} for ${limits.botId}`); - }); - - await client.Debugger.paused(({ callFrames, reason, hitBreakpoints }) => { + await client.Debugger.paused(async ({ callFrames, reason, hitBreakpoints }) => { const frame = callFrames[0]; - if (hitBreakpoints.length > 1) { - GBLog.info(`.gbdialog break at line ${frame.location.lineNumber + 1}`); // (zero-based) - const scope = `${frame.scopeChain[0].name} ${frame.scopeChain[0].object}`; + // Build variable list ignoring system variables of script. - GBServer.globals.debuggers[limits.botId].scope = scope; + const scopeObjectId = frame.scopeChain[2].object.objectId; + const variables = await Runtime.getProperties({ objectId: scopeObjectId }); + let variablesText = ''; + if (variables && variables.result) { + await CollectionUtil.asyncForEach(variables.result, async v => { + if (!DebuggerService.systemVariables.filter(x => x === v.name)[0]) { + if (v.value.value) { + variablesText = `${variablesText} \n ${v.name}: ${v.value.value}`; + } + } + }); + } + GBLog.info(`BASIC: Breakpoint variables: ${variablesText}`); // (zero-based) + + // Processes breakpoint hits. + + if (hitBreakpoints.length >= 1) { + GBLog.info(`BASIC: Break at line ${frame.location.lineNumber + 1}`); // (zero-based) + + GBServer.globals.debuggers[limits.botId].scope = variablesText; GBServer.globals.debuggers[limits.botId].state = 2; - } else if (reason === '') { - GBLog.info(`.gbdialog ${reason} at line ${frame.location.lineNumber + 1}`); // (zero-based) + } else { + GBLog.info(`BASIC: Configuring breakpoints if any for ${limits.botId}`); + // Waits for debugger and setup breakpoints. + + await CollectionUtil.asyncForEach(GBServer.globals.debuggers[limits.botId].breaks, async brk => { + try { + const { breakpointId } = await client.Debugger.setBreakpoint({ + location: { + scriptId: frame.location.scriptId, + lineNumber: brk + } + }); + GBLog.info(`BASIC break defined ${breakpointId} for ${limits.botId}`); + } catch (error) { + GBLog.info(`BASIC error defining defining ${brk} for ${limits.botId}. ${error}`); + } + }); + await client.Debugger.resume(); } }); - + await client.Runtime.runIfWaitingForDebugger(); + await client.Debugger.enable(); + await client.Runtime.enable(); + resolve(1); } catch (err) { GBLog.error(err); diff --git a/packages/core.gbapp/services/GBMinService.ts b/packages/core.gbapp/services/GBMinService.ts index 6a6cd9a0..22f80c76 100644 --- a/packages/core.gbapp/services/GBMinService.ts +++ b/packages/core.gbapp/services/GBMinService.ts @@ -945,7 +945,7 @@ export class GBMinService { if (startDialog && !user.welcomed) { user.welcomed = true; GBLog.info(`Auto start (teams) dialog is now being called: ${startDialog} for ${min.instance.botId}...`); - await GBVMService.callVM(startDialog.toLowerCase(), min, step, this.deployer); + await GBVMService.callVM(startDialog.toLowerCase(), min, step, this.deployer, false); } } } @@ -990,7 +990,7 @@ export class GBMinService { min["conversationWelcomed"][step.context.activity.conversation.id] = true; GBLog.info(`Auto start (web 1) dialog is now being called: ${startDialog} for ${min.instance.instanceId}...`); - await GBVMService.callVM(startDialog.toLowerCase(), min, step, this.deployer); + await GBVMService.callVM(startDialog.toLowerCase(), min, step, this.deployer, false); } } @@ -1004,7 +1004,7 @@ export class GBMinService { min["conversationWelcomed"][step.context.activity.conversation.id] = true; await min.userProfile.set(step.context, user); GBLog.info(`Auto start (whatsapp) dialog is now being called: ${startDialog} for ${min.instance.instanceId}...`); - await GBVMService.callVM(startDialog.toLowerCase(), min, step, this.deployer); + await GBVMService.callVM(startDialog.toLowerCase(), min, step, this.deployer, false); } } } @@ -1071,7 +1071,7 @@ export class GBMinService { if (startDialog && !min["conversationWelcomed"][step.context.activity.conversation.id]) { user.welcomed = true; GBLog.info(`Auto start (web 2) dialog is now being called: ${startDialog} for ${min.instance.instanceId}...`); - await GBVMService.callVM(startDialog.toLowerCase(), min, step, this.deployer); + await GBVMService.callVM(startDialog.toLowerCase(), min, step, this.deployer, false); } } else if (context.activity.name === 'updateToken') { const token = context.activity.data; @@ -1139,7 +1139,7 @@ export class GBMinService { const isVMCall = Object.keys(min.scriptMap).find(key => min.scriptMap[key] === context.activity.text) !== undefined; if (isVMCall) { - await GBVMService.callVM(context.activity.text, min, step, this.deployer); + await GBVMService.callVM(context.activity.text, min, step, this.deployer, false); } else if (context.activity.text.charAt(0) === '/') { const text = context.activity.text; @@ -1158,9 +1158,11 @@ export class GBMinService { await min.userProfile.set(step.context, user); } else if (cmdOrDialogName === '/call') { - await GBVMService.callVM(args, min, step, this.deployer); + await GBVMService.callVM(args, min, step, this.deployer, false); } else if (cmdOrDialogName === '/callsch') { - await GBVMService.callVM(args, min, null, null); + await GBVMService.callVM(args, min, null, null, false); + } else if (cmdOrDialogName === '/calldbg') { + await GBVMService.callVM(args, min, step, this.deployer, true); } else { await step.beginDialog(cmdOrDialogName, { args: args }); } diff --git a/packages/kb.gbapp/dialogs/AskDialog.ts b/packages/kb.gbapp/dialogs/AskDialog.ts index d9478993..b276218f 100644 --- a/packages/kb.gbapp/dialogs/AskDialog.ts +++ b/packages/kb.gbapp/dialogs/AskDialog.ts @@ -179,7 +179,7 @@ export class AskDialog extends IGBDialog { const startDialog = min.core.getParam(min.instance, 'Start Dialog', null); if (startDialog) { - await GBVMService.callVM(startDialog.toLowerCase().trim(), min, step, this.deployer); + await GBVMService.callVM(startDialog.toLowerCase().trim(), min, step, this.deployer, false); } return step.endDialog(); @@ -304,7 +304,7 @@ export class AskDialog extends IGBDialog { const mainName = GBVMService.getMethodNameFromVBSFilename(text); - return await GBVMService.callVM(mainName, min, step, this.deployer); + return await GBVMService.callVM(mainName, min, step, this.deployer, false); } else { await service.sendAnswer(min, AskDialog.getChannel(step), step, answer); return await step.replaceDialog('/ask', { isReturning: true });