diff --git a/package.json b/package.json index 7f8f41eb4..d8af279a4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "automa", - "version": "1.2.1", + "version": "1.3.0", "description": "An extension for automating your browser by connecting blocks", "license": "MIT", "repository": { diff --git a/src/background/collection-engine/flow-handler.js b/src/background/collection-engine/flow-handler.js index 370bbfc70..80ab2d63d 100644 --- a/src/background/collection-engine/flow-handler.js +++ b/src/background/collection-engine/flow-handler.js @@ -30,13 +30,15 @@ export function workflow(flow) { blocksHandler, states: this.states, logger: this.logger, - parentWorkflow: { - id: this.id, - isCollection: true, - name: this.collection.name, - }, - data: { - globalData: globalData.trim() === '' ? null : globalData, + options: { + parentWorkflow: { + id: this.id, + isCollection: true, + name: this.collection.name, + }, + data: { + globalData: globalData.trim() === '' ? null : globalData, + }, }, }); diff --git a/src/background/index.js b/src/background/index.js index c5ea9ee50..c02e7acbe 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -64,7 +64,7 @@ const workflow = { } const engine = new WorkflowEngine(workflowData, { - ...options, + options, blocksHandler, logger: this.logger, states: this.states, @@ -155,7 +155,7 @@ async function checkVisitWebTriggers(changeInfo, tab) { if (triggeredWorkflow) { const workflowData = await workflow.get(triggeredWorkflow.id); - if (workflowData) workflow.execute(workflowData); + if (workflowData) workflow.execute(workflowData, { tabId: tab.id }); } } async function checkRecordingWorkflow({ status }, { url, id }) { @@ -353,13 +353,18 @@ message.on('set:active-tab', (tabId) => { return browser.tabs.update(tabId, { active: true }); }); -message.on('get:sender', (_, sender) => { - return sender; -}); -message.on('get:tab-screenshot', (options) => { - return browser.tabs.captureVisibleTab(options); +message.on('debugger:send-command', ({ tabId, method, params }) => { + return new Promise((resolve) => { + console.log(tabId, method, params); + chrome.debugger.sendCommand({ tabId }, method, params, resolve); + }); }); + +message.on('get:sender', (_, sender) => sender); message.on('get:file', (path) => getFile(path)); +message.on('get:tab-screenshot', (options) => + browser.tabs.captureVisibleTab(options) +); message.on('collection:execute', (collection) => { const engine = new CollectionEngine(collection, { diff --git a/src/background/workflow-engine/blocks-handler/handler-execute-workflow.js b/src/background/workflow-engine/blocks-handler/handler-execute-workflow.js index 31cd5e7cb..3af9deb36 100644 --- a/src/background/workflow-engine/blocks-handler/handler-execute-workflow.js +++ b/src/background/workflow-engine/blocks-handler/handler-execute-workflow.js @@ -52,9 +52,14 @@ async function executeWorkflow({ outputs, data }) { throw errorInstance; } const options = { - parentWorkflow: { - id: this.id, - name: this.workflow.name, + options: { + data: { + globalData: isWhitespace(data.globalData) ? null : data.globalData, + }, + parentWorkflow: { + id: this.id, + name: this.workflow.name, + }, }, events: { onInit: (engine) => { @@ -77,9 +82,6 @@ async function executeWorkflow({ outputs, data }) { states: this.states, logger: this.logger, blocksHandler: this.blocksHandler, - data: { - globalData: isWhitespace(data.globalData) ? null : data.globalData, - }, }; if (workflow.drawflow.includes(this.workflow.id)) { diff --git a/src/background/workflow-engine/blocks-handler/handler-hover-element.js b/src/background/workflow-engine/blocks-handler/handler-hover-element.js new file mode 100644 index 000000000..b20ded0b7 --- /dev/null +++ b/src/background/workflow-engine/blocks-handler/handler-hover-element.js @@ -0,0 +1,38 @@ +import { getBlockConnection, attachDebugger } from '../helper'; + +export async function hoverElement(block) { + const nextBlockId = getBlockConnection(block); + + try { + if (!this.activeTab.id) throw new Error('no-tab'); + + const { debugMode, executedBlockOnWeb } = this.workflow.settings; + + if (!debugMode) { + await attachDebugger(this.activeTab.id); + } + + await this._sendMessageToTab({ + ...block, + debugMode, + executedBlockOnWeb, + activeTabId: this.activeTab.id, + frameSelector: this.frameSelector, + }); + + if (!debugMode) { + chrome.debugger.detach({ tabId: this.activeTab.id }); + } + + return { + data: '', + nextBlockId, + }; + } catch (error) { + error.nextBlockId = nextBlockId; + + throw error; + } +} + +export default hoverElement; diff --git a/src/background/workflow-engine/blocks-handler/handler-interaction-block.js b/src/background/workflow-engine/blocks-handler/handler-interaction-block.js index f721b8827..594c10abd 100644 --- a/src/background/workflow-engine/blocks-handler/handler-interaction-block.js +++ b/src/background/workflow-engine/blocks-handler/handler-interaction-block.js @@ -32,12 +32,14 @@ async function interactionHandler(block, { refData }) { const nextBlockId = getBlockConnection(block); const messagePayload = { ...block, - refData, debugMode, executedBlockOnWeb, + activeTabId: this.activeTab.id, frameSelector: this.frameSelector, }; + if (block.name === 'javascript-code') messagePayload.refData = refData; + try { const data = await this._sendMessageToTab(messagePayload, { frameId: this.activeTab.frameId || 0, @@ -80,13 +82,7 @@ async function interactionHandler(block, { refData }) { } } - const isJavascriptBlock = block.name === 'javascript-code'; - - if (block.data.assignVariable && !isJavascriptBlock) { - this.referenceData.variables[block.data.variableName] = data; - } - - if (isJavascriptBlock) { + if (block.name === 'javascript-code') { if (data?.variables) { Object.keys(data.variables).forEach((varName) => { this.referenceData.variables[varName] = data.variables[varName]; @@ -99,6 +95,8 @@ async function interactionHandler(block, { refData }) { : [data.columns.data]; this.addDataToColumn(arrData); } + } else if (block.data.assignVariable) { + this.referenceData.variables[block.data.variableName] = data; } return { diff --git a/src/background/workflow-engine/blocks-handler/handler-new-tab.js b/src/background/workflow-engine/blocks-handler/handler-new-tab.js index 798c0abc8..85cf4d86e 100644 --- a/src/background/workflow-engine/blocks-handler/handler-new-tab.js +++ b/src/background/workflow-engine/blocks-handler/handler-new-tab.js @@ -1,5 +1,9 @@ import browser from 'webextension-polyfill'; -import { getBlockConnection } from '../helper'; +import { + getBlockConnection, + attachDebugger, + sendDebugCommand, +} from '../helper'; import { isWhitespace } from '@/utils/helper'; async function newTab(block) { @@ -14,7 +18,8 @@ async function newTab(block) { const nextBlockId = getBlockConnection(block); try { - const { updatePrevTab, url, active, inGroup } = block.data; + const { updatePrevTab, url, active, inGroup, customUserAgent, userAgent } = + block.data; const isInvalidUrl = !/^https?/.test(url); if (isInvalidUrl) { @@ -40,6 +45,21 @@ async function newTab(block) { this.activeTab.url = url; if (tab) { + if (this.workflow.settings.debugMode || customUserAgent) { + await attachDebugger(tab.id, this.activeTab.id); + + if (customUserAgent) { + const res = await sendDebugCommand( + tab.id, + 'Network.setUserAgentOverride', + { + userAgent, + } + ); + console.log('agent:', res); + } + } + this.activeTab.id = tab.id; this.windowId = tab.windowId; } @@ -63,6 +83,10 @@ async function newTab(block) { this.activeTab.frameId = 0; + if (!this.workflow.settings.debugMode && customUserAgent) { + chrome.debugger.detach({ tabId: tab.id }); + } + return { data: url, nextBlockId, diff --git a/src/background/workflow-engine/blocks-handler/handler-switch-tab.js b/src/background/workflow-engine/blocks-handler/handler-switch-tab.js index 6bea48046..5611c7aea 100644 --- a/src/background/workflow-engine/blocks-handler/handler-switch-tab.js +++ b/src/background/workflow-engine/blocks-handler/handler-switch-tab.js @@ -1,5 +1,5 @@ import browser from 'webextension-polyfill'; -import { getBlockConnection } from '../helper'; +import { getBlockConnection, attachDebugger } from '../helper'; export default async function ({ data, outputs }) { const nextBlockId = getBlockConnection({ outputs }); @@ -32,6 +32,10 @@ export default async function ({ data, outputs }) { await browser.tabs.update(tab.id, { active: true }); } + if (this.workflow.settings.debugMode) { + await attachDebugger(tab.id, this.activeTab.id); + } + this.activeTab.id = tab.id; this.activeTab.frameId = 0; this.activeTab.url = tab.url; diff --git a/src/background/workflow-engine/engine.js b/src/background/workflow-engine/engine.js index 58eb262c8..b18beee13 100644 --- a/src/background/workflow-engine/engine.js +++ b/src/background/workflow-engine/engine.js @@ -2,14 +2,20 @@ import browser from 'webextension-polyfill'; import { nanoid } from 'nanoid'; import { tasks } from '@/utils/shared'; import { convertData, waitTabLoaded } from './helper'; -import { toCamelCase, parseJSON, isObject, objectHasKey } from '@/utils/helper'; +import { + toCamelCase, + sleep, + parseJSON, + isObject, + objectHasKey, +} from '@/utils/helper'; import referenceData from '@/utils/reference-data'; import executeContentScript from './execute-content-script'; class WorkflowEngine { constructor( workflow, - { states, logger, blocksHandler, tabId, parentWorkflow, data } + { states, logger, blocksHandler, parentWorkflow, options } ) { this.id = nanoid(); this.states = states; @@ -23,6 +29,7 @@ class WorkflowEngine { this.repeatedTasks = {}; this.windowId = null; + this.triggerBlock = null; this.currentBlock = null; this.childWorkflowId = null; @@ -34,15 +41,20 @@ class WorkflowEngine { this.eventListeners = {}; this.columns = { column: { index: 0, type: 'any' } }; - const globalData = data?.globalData || workflow.globalData; - const variables = isObject(data?.variables) ? data.variables : {}; + const globalData = options?.data?.globalData || workflow.globalData; + const variables = isObject(options?.data?.variables) + ? options?.data.variables + : {}; + + options.data = { globalData, variables }; + this.options = options; this.activeTab = { url: '', - id: tabId, frameId: 0, frames: {}, groupId: null, + id: options?.tabId, }; this.referenceData = { variables, @@ -59,7 +71,38 @@ class WorkflowEngine { }; } - init(currentBlock) { + reset() { + this.loopList = {}; + this.repeatedTasks = {}; + + this.windowId = null; + this.currentBlock = null; + this.childWorkflowId = null; + + this.isDestroyed = false; + this.isUsingProxy = false; + + this.history = []; + this.columns = { column: { index: 0, type: 'any' } }; + + this.activeTab = { + url: '', + frameId: 0, + frames: {}, + groupId: null, + id: this.options?.tabId, + }; + this.referenceData = { + table: [], + loopData: {}, + workflow: {}, + googleSheets: {}, + variables: this.options.variables, + globalData: this.referenceData.globalData, + }; + } + + init() { if (this.workflow.isDisabled) return; if (!this.states) { @@ -98,7 +141,7 @@ class WorkflowEngine { this.blocks = blocks; this.startedTimestamp = Date.now(); this.workflow.table = columns; - this.currentBlock = currentBlock || triggerBlock; + this.currentBlock = triggerBlock; this.states.on('stop', this.onWorkflowStopped); @@ -196,6 +239,11 @@ class WorkflowEngine { try { if (this.isDestroyed) return; if (this.isUsingProxy) chrome.proxy.settings.clear({}); + if (this.workflow.settings.debugMode && this.activeTab.id) { + await sleep(1000); + + chrome.debugger.detach({ tabId: this.activeTab.id }); + } const endedTimestamp = Date.now(); this.executeQueue(); @@ -255,7 +303,8 @@ class WorkflowEngine { this.dispatchEvent('update', { state: this.state }); const startExecutedTime = Date.now(); - const blockHandler = this.blocksHandler[toCamelCase(block?.name)]; + + const blockHandler = this.blocksHandler[toCamelCase(block.name)]; const handler = !blockHandler && tasks[block.name].category === 'interaction' ? this.blocksHandler.interactionBlock @@ -307,13 +356,31 @@ class WorkflowEngine { ...(error.data || {}), }); - if ( - this.workflow.settings.onError === 'keep-running' && - error.nextBlockId - ) { + const { onError } = this.workflow.settings; + + if (onError === 'keep-running' && error.nextBlockId) { setTimeout(() => { this.executeBlock(this.blocks[error.nextBlockId], error.data || ''); }, blockDelay); + } else if (onError === 'restart-workflow' && !this.parentWorkflow) { + const restartKey = `restart-count:${this.id}`; + const restartCount = +localStorage.getItem(restartKey) || 0; + const maxRestart = this.workflow.settings.restartTimes ?? 3; + + if (restartCount >= maxRestart) { + localStorage.removeItem(restartKey); + this.destroy(); + return; + } + + this.reset(); + + const triggerBlock = Object.values(this.blocks).find( + ({ name }) => name === 'trigger' + ); + this.executeBlock(triggerBlock); + + localStorage.setItem(restartKey, restartCount + 1); } else { this.destroy('error', error.message); } diff --git a/src/background/workflow-engine/helper.js b/src/background/workflow-engine/helper.js index 629c4e06d..0583f5787 100644 --- a/src/background/workflow-engine/helper.js +++ b/src/background/workflow-engine/helper.js @@ -1,3 +1,18 @@ +export function sendDebugCommand(tabId, method, params = {}) { + return new Promise((resolve) => { + chrome.debugger.sendCommand({ tabId }, method, params, resolve); + }); +} + +export function attachDebugger(tabId, prevTab) { + return new Promise((resolve) => { + if (prevTab && tabId !== prevTab) + chrome.debugger.detach({ tabId: prevTab }); + + chrome.debugger.attach({ tabId }, '1.3', resolve); + }); +} + export function waitTabLoaded(tabId) { return new Promise((resolve, reject) => { const activeTabStatus = () => { diff --git a/src/components/newtab/logs/LogsFilters.vue b/src/components/newtab/logs/LogsFilters.vue index 2edda821f..b900a4ffe 100644 --- a/src/components/newtab/logs/LogsFilters.vue +++ b/src/components/newtab/logs/LogsFilters.vue @@ -68,6 +68,13 @@ + + +