diff --git a/.travis.yml b/.travis.yml index 158ca137..8b252f2a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ dist: focal language: node_js node_js: - - 19.5.0 + - 19.6.0 notifications: diff --git a/package.json b/package.json index 033f81bf..f6a40293 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "Dário Vieira " ], "engines": { - "node": "=19.5.0" + "node": "=19.6.0" }, "license": "AGPL-3.0", "preferGlobal": true, @@ -76,6 +76,7 @@ "alasql": "2.1.6", "any-shell-escape": "0.1.1", "arraybuffer-to-buffer": "^0.0.7", + "async-mutex": "^0.4.0", "async-promises": "0.2.3", "basic-auth": "2.0.1", "billboard.js": "3.6.3", diff --git a/packages/basic.gblib/services/KeywordsExpressions.ts b/packages/basic.gblib/services/KeywordsExpressions.ts index 8df1cd92..e5241c31 100644 --- a/packages/basic.gblib/services/KeywordsExpressions.ts +++ b/packages/basic.gblib/services/KeywordsExpressions.ts @@ -140,12 +140,21 @@ export class KeywordsExpressions { keywords[i++] = [ /^\s*open\s*(.*)/gim, ($0, $1, $2) => { + + let pos; + let sessionName; + if (pos = $1.match(/\s*AS\s*\#/)){ + let part = $1.substr($1.lastIndexOf(pos[0])); + sessionName = `"${part.substr(part.indexOf("#") + 1)}"`; + $1 = $1.substr(0, $1.lastIndexOf(pos[0])); + } + if (!$1.startsWith('"') && !$1.startsWith("'")) { $1 = `"${$1}"`; } const params = this.getParams($1, ['url', 'username', 'password']); - - return `page = await wa.getPage({pid: pid,${params}})`; + + return `page = await wa.getPage({pid: pid, sessionName: ${sessionName}, ${params}})`; } ]; diff --git a/packages/basic.gblib/services/WebAutomationServices.ts b/packages/basic.gblib/services/WebAutomationServices.ts index d2e873fb..dae6dad5 100644 --- a/packages/basic.gblib/services/WebAutomationServices.ts +++ b/packages/basic.gblib/services/WebAutomationServices.ts @@ -44,12 +44,15 @@ import urlJoin from 'url-join'; import Fs from 'fs'; import Path from 'path'; import url from 'url'; -import { pid } from 'process'; +import {Mutex, Semaphore, withTimeout} from 'async-mutex'; /** * Web Automation services of conversation to be called by BASIC. */ export class WebAutomationServices { + + static semaphoreWithTimeout = withTimeout(new Semaphore(5), 60 * 1000, new Error('new fancy error')); + /** * Reference to minimal bot instance. */ @@ -107,7 +110,7 @@ export class WebAutomationServices { * When creating this keyword facade,a bot instance is * specified among the deployer service. */ - constructor (min: GBMinInstance, user, dk) { + constructor(min: GBMinInstance, user, dk) { this.min = min; this.user = user; this.dk = dk; @@ -120,25 +123,37 @@ export class WebAutomationServices { * * @example OPEN "https://wikipedia.org" */ - public async getPage ({ pid, url, username, password }) { - GBLog.info(`BASIC: Web Automation GET PAGE ${url}.`); - if (!this.browser) { - this.browser = await createBrowser(null); - } - const page = (await this.browser.pages())[0]; - if (username || password) { - await page.authenticate({pid, username: username, password: password }); + + public async getPage({ pid, sessionName, url, username, password }) { + GBLog.info(`BASIC: Web Automation GET PAGE ${sessionName ? sessionName : ''} ${url}.`); + + let page; + if (url.startsWith('#')) { + const [value, release] = await WebAutomationServices.semaphoreWithTimeout.acquire(); + try { + page = GBServer.globals.webSessions[url.substr(1)]; + } finally { + release(); + } + } else { + if (!this.browser) { + this.browser = await createBrowser(null); + } + page = (await this.browser.pages())[0]; + if (sessionName) { + GBServer.globals.webSessions[sessionName] = page; + } + if (username || password) { + await page.authenticate({ pid, username: username, password: password }); + } } await page.goto(url); - const handle = WebAutomationServices.cyrb53(this.min.botId + url); - this.pageMap[handle] = page; - return handle; } - public getPageByHandle (hash) { + public getPageByHandle(hash) { return this.pageMap[hash]; } @@ -147,7 +162,7 @@ export class WebAutomationServices { * * @example GET "selector" */ - public async getBySelector ({ handle, selector }) { + public async getBySelector({ handle, selector }) { const page = this.getPageByHandle(handle); GBLog.info(`BASIC: Web Automation GET element: ${selector}.`); await page.waitForSelector(selector); @@ -170,7 +185,7 @@ export class WebAutomationServices { * * @example GET page,"frameSelector,"elementSelector" */ - public async getByFrame ({ handle, frame, selector }) { + public async getByFrame({ handle, frame, selector }) { const page = this.getPageByHandle(handle); GBLog.info(`BASIC: Web Automation GET element by frame: ${selector}.`); await page.waitForSelector(frame); @@ -190,7 +205,7 @@ export class WebAutomationServices { /** * Simulates a mouse hover an web page element. */ - public async hover ({ pid, handle, selector }) { + public async hover({ pid, handle, selector }) { const page = this.getPageByHandle(handle); GBLog.info(`BASIC: Web Automation HOVER element: ${selector}.`); await this.getBySelector({ handle, selector: selector }); @@ -203,7 +218,7 @@ export class WebAutomationServices { * * @example CLICK page,"#idElement" */ - public async click ({ pid, handle, frameOrSelector, selector }) { + public async click({ pid, handle, frameOrSelector, selector }) { const page = this.getPageByHandle(handle); GBLog.info(`BASIC: Web Automation CLICK element: ${frameOrSelector}.`); if (selector) { @@ -219,7 +234,7 @@ export class WebAutomationServices { await this.debugStepWeb(pid, page); } - private async debugStepWeb (pid, page) { + private async debugStepWeb(pid, page) { let refresh = true; if (this.lastDebugWeb) { refresh = new Date().getTime() - this.lastDebugWeb.getTime() > 5000; @@ -229,7 +244,7 @@ export class WebAutomationServices { const mobile = this.min.core.getParam(this.min.instance, 'Bot Admin Number', null); const filename = page; if (mobile) { - await this.dk.sendFileTo({pid: pid, mobile, filename, caption: 'General Bots Debugger' }); + await this.dk.sendFileTo({ pid: pid, mobile, filename, caption: 'General Bots Debugger' }); } this.lastDebugWeb = new Date(); } @@ -240,7 +255,7 @@ export class WebAutomationServices { * * @example PRESS ENTER ON page */ - public async pressKey ({ handle, char, frame }) { + public async pressKey({ handle, char, frame }) { const page = this.getPageByHandle(handle); GBLog.info(`BASIC: Web Automation PRESS ${char} ON element: ${frame}.`); if (char.toLowerCase() === 'enter') { @@ -256,7 +271,7 @@ export class WebAutomationServices { } } - public async linkByText ({ pid, handle, text, index }) { + public async linkByText({ pid, handle, text, index }) { const page = this.getPageByHandle(handle); GBLog.info(`BASIC: Web Automation CLICK LINK TEXT: ${text} ${index}.`); if (!index) { @@ -272,7 +287,7 @@ export class WebAutomationServices { * * @example file = SCREENSHOT page */ - public async screenshot ({ handle, selector }) { + public async screenshot({ handle, selector }) { const page = this.getPageByHandle(handle); GBLog.info(`BASIC: Web Automation SCREENSHOT ${selector}.`); @@ -292,7 +307,7 @@ export class WebAutomationServices { * * @example SET page,"selector","text" */ - public async setElementText ({ pid, handle, selector, text }) { + public async setElementText({ pid, handle, selector, text }) { const page = this.getPageByHandle(handle); GBLog.info(`BASIC: Web Automation TYPE on ${selector}: ${text}.`); const e = await this.getBySelector({ handle, selector }); @@ -307,9 +322,9 @@ export class WebAutomationServices { * * @example file = DOWNLOAD element, folder */ - public async download ({ handle, selector, folder }) { + public async download({ handle, selector, folder }) { const page = this.getPageByHandle(handle); - + const element = await this.getBySelector({ handle, selector }); // https://github.com/GeneralBots/BotServer/issues/311 const container = element['_frame'] ? element['_frame'] : element['_page']; diff --git a/packages/core.gbapp/services/GBLogEx.ts b/packages/core.gbapp/services/GBLogEx.ts index 37f5a03b..5a1b0fc8 100644 --- a/packages/core.gbapp/services/GBLogEx.ts +++ b/packages/core.gbapp/services/GBLogEx.ts @@ -37,7 +37,7 @@ 'use strict'; import { GBLog, IGBInstance } from "botlib"; -import { GuaribasLog } from "../models/GBModel"; +import { GuaribasLog } from "../models/GBModel.js"; export class GBLogEx { public static async error(minOrInstanceId: any, message: string) { @@ -76,6 +76,7 @@ export class GBLogEx { * Finds and update user agent information to a next available person. */ public static async log(instance: IGBInstance, kind: string, message: string): Promise { + message = message?message.substring(0,1023):null; return await GuaribasLog.create({ instanceId: instance.instanceId, message: message, diff --git a/packages/core.gbapp/services/GBSSR.ts b/packages/core.gbapp/services/GBSSR.ts index 3148c4c7..8f361f13 100644 --- a/packages/core.gbapp/services/GBSSR.ts +++ b/packages/core.gbapp/services/GBSSR.ts @@ -36,11 +36,17 @@ 'use strict'; -import puppeteer from 'puppeteer-extra'; + +import {createRequire} from "module"; +const require = createRequire(import.meta.url); + +const puppeteer = require('puppeteer-extra'); +const hidden = require('puppeteer-extra-plugin-stealth') + +// require executablePath from puppeteer +const {executablePath} = require('puppeteer') import Fs from 'fs'; -// const StealthPlugin from 'puppeteer-extra-plugin-stealth') -// puppeteer.use(StealthPlugin()); import { NextFunction, Request, Response } from 'express'; import urljoin from 'url-join'; @@ -96,12 +102,13 @@ async function createBrowser (profilePath): Promise { Fs.writeFileSync(preferences, JSON.stringify(data)); } } - + puppeteer.use(hidden()) const browser = await puppeteer.launch({ args: args, ignoreHTTPSErrors: true, headless: false, defaultViewport: null, + executablePath:executablePath(), ignoreDefaultArgs: ['--enable-automation', '--enable-blink-features=IdleDetection'] }); return browser; diff --git a/src/RootData.ts b/src/RootData.ts index 2cc40292..bf89456f 100644 --- a/src/RootData.ts +++ b/src/RootData.ts @@ -42,6 +42,7 @@ import { GBMinService } from '../packages/core.gbapp/services/GBMinService.js'; */ export class RootData { + 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. diff --git a/src/app.ts b/src/app.ts index eb4c41f8..ba141904 100644 --- a/src/app.ts +++ b/src/app.ts @@ -84,6 +84,7 @@ export class GBServer { const server = express(); GBServer.globals.server = server; + GBServer.globals.webSessions = {}; GBServer.globals.processes = {}; GBServer.globals.files = {}; GBServer.globals.appPackages = [];