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 @@
+
+
+