|
@@ -1,172 +1,143 @@
|
|
|
-/* eslint-disable no-underscore-dangle */
|
|
|
import browser from 'webextension-polyfill';
|
|
|
import { nanoid } from 'nanoid';
|
|
|
import { tasks } from '@/utils/shared';
|
|
|
import { convertData } from './helper';
|
|
|
-import { generateJSON } from '@/utils/data-exporter';
|
|
|
import { toCamelCase, parseJSON, isObject, objectHasKey } from '@/utils/helper';
|
|
|
-import errorMessage from './error-message';
|
|
|
import referenceData from '@/utils/reference-data';
|
|
|
-import workflowState from '../workflow-state';
|
|
|
import executeContentScript from './execute-content-script';
|
|
|
|
|
|
-let reloadTimeout;
|
|
|
-
|
|
|
-function tabRemovedHandler(tabId) {
|
|
|
- if (tabId !== this.tabId) return;
|
|
|
-
|
|
|
- delete this.tabId;
|
|
|
-
|
|
|
- if (
|
|
|
- this.currentBlock.name === 'new-tab' ||
|
|
|
- tasks[this.currentBlock.name].category === 'interaction'
|
|
|
- ) {
|
|
|
- this.destroy('error', 'active-tab-removed');
|
|
|
- }
|
|
|
-
|
|
|
- workflowState.update(this.id, this.state);
|
|
|
-}
|
|
|
-function tabUpdatedHandler(tabId, changeInfo, tab) {
|
|
|
- const listener = this.tabUpdatedListeners[tabId];
|
|
|
-
|
|
|
- if (listener) {
|
|
|
- listener.callback(tabId, changeInfo, () => {
|
|
|
- delete this.tabUpdatedListeners[tabId];
|
|
|
- });
|
|
|
- } else if (this.tabId === tabId) {
|
|
|
- if (!reloadTimeout) {
|
|
|
- reloadTimeout = setTimeout(() => {
|
|
|
- this.isPaused = false;
|
|
|
- }, 15000);
|
|
|
- }
|
|
|
-
|
|
|
- this.isPaused = true;
|
|
|
-
|
|
|
- if (changeInfo.status === 'complete') {
|
|
|
- clearTimeout(reloadTimeout);
|
|
|
- reloadTimeout = null;
|
|
|
-
|
|
|
- executeContentScript(tabId)
|
|
|
- .then((frames) => {
|
|
|
- this.tabId = tabId;
|
|
|
- this.frames = frames;
|
|
|
- this.isPaused = false;
|
|
|
- this.activeTabUrl = tab?.url || '';
|
|
|
- })
|
|
|
- .catch((error) => {
|
|
|
- console.error(error);
|
|
|
- this.isPaused = false;
|
|
|
- });
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
class WorkflowEngine {
|
|
|
constructor(
|
|
|
workflow,
|
|
|
- {
|
|
|
- globalData,
|
|
|
- tabId = null,
|
|
|
- isInCollection,
|
|
|
- collectionLogId,
|
|
|
- blocksHandler,
|
|
|
- parentWorkflow,
|
|
|
- }
|
|
|
+ { states, logger, blocksHandler, tabId, parentWorkflow, globalData }
|
|
|
) {
|
|
|
- const globalDataVal = globalData || workflow.globalData;
|
|
|
-
|
|
|
this.id = nanoid();
|
|
|
- this.tabId = tabId;
|
|
|
+ this.states = states;
|
|
|
+ this.logger = logger;
|
|
|
this.workflow = workflow;
|
|
|
this.blocksHandler = blocksHandler;
|
|
|
- this.isInCollection = isInCollection;
|
|
|
this.parentWorkflow = parentWorkflow;
|
|
|
- this.collectionLogId = collectionLogId;
|
|
|
- this.globalData = parseJSON(globalDataVal, globalDataVal);
|
|
|
- this.activeTabUrl = '';
|
|
|
- this.columns = { column: { index: 0, type: 'any' } };
|
|
|
- this.data = [];
|
|
|
- this.logs = [];
|
|
|
- this.blocks = {};
|
|
|
- this.frames = {};
|
|
|
+ this.saveLog = workflow.settings?.saveLog ?? true;
|
|
|
+
|
|
|
this.loopList = {};
|
|
|
- this.loopData = {};
|
|
|
this.repeatedTasks = {};
|
|
|
- this.eventListeners = {};
|
|
|
- this.isPaused = false;
|
|
|
- this.isDestroyed = false;
|
|
|
- this.isUsingProxy = false;
|
|
|
- this.frameId = 0;
|
|
|
+
|
|
|
this.windowId = null;
|
|
|
- this.tabGroupId = null;
|
|
|
this.currentBlock = null;
|
|
|
- this.childWorkflow = null;
|
|
|
- this.workflowTimeout = null;
|
|
|
+ this.childWorkflowId = null;
|
|
|
|
|
|
- this.saveLog = workflow.settings?.saveLog ?? true;
|
|
|
+ this.isDestroyed = false;
|
|
|
+ this.isUsingProxy = false;
|
|
|
+
|
|
|
+ this.blocks = {};
|
|
|
+ this.history = [];
|
|
|
+ this.eventListeners = {};
|
|
|
+ this.columns = { column: { index: 0, type: 'any' } };
|
|
|
|
|
|
- this.googleSheets = {};
|
|
|
+ const globalDataValue = globalData || workflow.globalData;
|
|
|
|
|
|
- this.tabUpdatedListeners = {};
|
|
|
- this.tabUpdatedHandler = tabUpdatedHandler.bind(this);
|
|
|
- this.tabRemovedHandler = tabRemovedHandler.bind(this);
|
|
|
+ this.activeTab = {
|
|
|
+ url: '',
|
|
|
+ id: tabId,
|
|
|
+ frameId: 0,
|
|
|
+ frames: {},
|
|
|
+ groupId: null,
|
|
|
+ };
|
|
|
+ this.referenceData = {
|
|
|
+ loopData: {},
|
|
|
+ dataColumns: [],
|
|
|
+ googleSheets: {},
|
|
|
+ globalData: parseJSON(globalDataValue, globalDataValue),
|
|
|
+ };
|
|
|
+
|
|
|
+ this.onWorkflowStopped = (id) => {
|
|
|
+ if (this.id !== id || this.isDestroyed) return;
|
|
|
+
|
|
|
+ this.stop();
|
|
|
+ };
|
|
|
}
|
|
|
|
|
|
- init() {
|
|
|
+ init(currentBlock) {
|
|
|
if (this.workflow.isDisabled) return;
|
|
|
|
|
|
- const drawflowData =
|
|
|
- typeof this.workflow.drawflow === 'string'
|
|
|
- ? JSON.parse(this.workflow.drawflow || '{}')
|
|
|
- : this.workflow.drawflow;
|
|
|
- const blocks = drawflowData?.drawflow?.Home.data;
|
|
|
+ if (!this.states) {
|
|
|
+ console.error(`"${this.workflow.name}" workflow doesn't have states`);
|
|
|
+ this.destroy('error');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const { drawflow } = this.workflow;
|
|
|
+ const flow =
|
|
|
+ typeof drawflow === 'string' ? parseJSON(drawflow, {}) : drawflow;
|
|
|
+ const blocks = flow?.drawflow?.Home.data;
|
|
|
|
|
|
if (!blocks) {
|
|
|
- console.error(errorMessage('no-block', this.workflow));
|
|
|
+ console.error(`${this.workflow.name} doesn't have blocks`);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- const blocksArr = Object.values(blocks);
|
|
|
- const triggerBlock = blocksArr.find(({ name }) => name === 'trigger');
|
|
|
-
|
|
|
+ const triggerBlock = Object.values(blocks).find(
|
|
|
+ ({ name }) => name === 'trigger'
|
|
|
+ );
|
|
|
if (!triggerBlock) {
|
|
|
- console.error(errorMessage('no-trigger-block', this.workflow));
|
|
|
+ console.error(`${this.workflow.name} doesn't have a trigger block`);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- browser.tabs.onUpdated.addListener(this.tabUpdatedHandler);
|
|
|
- browser.tabs.onRemoved.addListener(this.tabRemovedHandler);
|
|
|
-
|
|
|
const dataColumns = Array.isArray(this.workflow.dataColumns)
|
|
|
? this.workflow.dataColumns
|
|
|
: Object.values(this.workflow.dataColumns);
|
|
|
|
|
|
+ dataColumns.forEach(({ name, type }) => {
|
|
|
+ this.columns[name] = { index: 0, type };
|
|
|
+ });
|
|
|
+
|
|
|
this.blocks = blocks;
|
|
|
this.startedTimestamp = Date.now();
|
|
|
this.workflow.dataColumns = dataColumns;
|
|
|
+ this.currentBlock = currentBlock || triggerBlock;
|
|
|
|
|
|
- dataColumns.forEach(({ name, type }) => {
|
|
|
- this.columns[name] = { index: 0, type };
|
|
|
- });
|
|
|
+ this.states.on('stop', this.onWorkflowStopped);
|
|
|
|
|
|
- workflowState
|
|
|
+ this.states
|
|
|
.add(this.id, {
|
|
|
state: this.state,
|
|
|
workflowId: this.workflow.id,
|
|
|
- isInCollection: this.isInCollection,
|
|
|
+ parentState: this.parentWorkflow,
|
|
|
})
|
|
|
.then(() => {
|
|
|
- this._blockHandler(triggerBlock);
|
|
|
+ this.executeBlock(this.currentBlock);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- addData(key, value) {
|
|
|
+ resume({ id, state }) {
|
|
|
+ this.id = id;
|
|
|
+
|
|
|
+ Object.keys(state).forEach((key) => {
|
|
|
+ this[key] = state[key];
|
|
|
+ });
|
|
|
+
|
|
|
+ this.init(state.currentBlock);
|
|
|
+ }
|
|
|
+
|
|
|
+ addLogHistory(detail) {
|
|
|
+ if (
|
|
|
+ !this.saveLog &&
|
|
|
+ (this.history.length >= 1001 || detail.name === 'blocks-group') &&
|
|
|
+ detail.type !== 'error'
|
|
|
+ )
|
|
|
+ return;
|
|
|
+
|
|
|
+ this.history.push(detail);
|
|
|
+ }
|
|
|
+
|
|
|
+ addDataToColumn(key, value) {
|
|
|
if (Array.isArray(key)) {
|
|
|
key.forEach((item) => {
|
|
|
if (!isObject(item)) return;
|
|
|
|
|
|
Object.entries(item).forEach(([itemKey, itemValue]) => {
|
|
|
- this.addData(itemKey, itemValue);
|
|
|
+ this.addDataToColumn(itemKey, itemValue);
|
|
|
});
|
|
|
});
|
|
|
|
|
@@ -177,50 +148,22 @@ class WorkflowEngine {
|
|
|
const currentColumn = this.columns[columnName];
|
|
|
const convertedValue = convertData(value, currentColumn.type);
|
|
|
|
|
|
- if (objectHasKey(this.data, currentColumn.index)) {
|
|
|
- this.data[currentColumn.index][columnName] = convertedValue;
|
|
|
+ if (objectHasKey(this.referenceData.dataColumns, currentColumn.index)) {
|
|
|
+ this.referenceData.dataColumns[currentColumn.index][columnName] =
|
|
|
+ convertedValue;
|
|
|
} else {
|
|
|
- this.data.push({ [columnName]: convertedValue });
|
|
|
+ this.referenceData.dataColumns.push({ [columnName]: convertedValue });
|
|
|
}
|
|
|
|
|
|
currentColumn.index += 1;
|
|
|
}
|
|
|
|
|
|
- addLog(detail) {
|
|
|
- if (
|
|
|
- !this.saveLog &&
|
|
|
- (this.logs.length >= 1001 || detail.name === 'blocks-group') &&
|
|
|
- detail.type !== 'error'
|
|
|
- )
|
|
|
- return;
|
|
|
-
|
|
|
- this.logs.push(detail);
|
|
|
- }
|
|
|
-
|
|
|
- on(name, listener) {
|
|
|
- (this.eventListeners[name] = this.eventListeners[name] || []).push(
|
|
|
- listener
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- pause(pause = true) {
|
|
|
- this.isPaused = pause;
|
|
|
-
|
|
|
- workflowState.update(this.id, this.state);
|
|
|
- }
|
|
|
-
|
|
|
- async stop(message) {
|
|
|
+ async stop() {
|
|
|
try {
|
|
|
- if (this.childWorkflow) {
|
|
|
- await this.childWorkflow.stop();
|
|
|
+ if (this.childWorkflowId) {
|
|
|
+ await this.states.stop(this.childWorkflowId);
|
|
|
}
|
|
|
|
|
|
- this.addLog({
|
|
|
- message,
|
|
|
- type: 'stop',
|
|
|
- name: 'stop',
|
|
|
- });
|
|
|
-
|
|
|
await this.destroy('stopped');
|
|
|
} catch (error) {
|
|
|
console.error(error);
|
|
@@ -229,55 +172,123 @@ class WorkflowEngine {
|
|
|
|
|
|
async destroy(status, message) {
|
|
|
try {
|
|
|
+ if (this.isDestroyed) return;
|
|
|
if (this.isUsingProxy) chrome.proxy.settings.clear({});
|
|
|
|
|
|
- await browser.tabs.onRemoved.removeListener(this.tabRemovedHandler);
|
|
|
- await browser.tabs.onUpdated.removeListener(this.tabUpdatedHandler);
|
|
|
-
|
|
|
- await workflowState.delete(this.id);
|
|
|
-
|
|
|
- clearTimeout(this.workflowTimeout);
|
|
|
- this.isDestroyed = true;
|
|
|
- this.endedTimestamp = Date.now();
|
|
|
+ const endedTimestamp = Date.now();
|
|
|
|
|
|
if (!this.workflow.isTesting && this.saveLog) {
|
|
|
- const { logs } = await browser.storage.local.get('logs');
|
|
|
- const { name, icon, id } = this.workflow;
|
|
|
- const jsonData = generateJSON(Object.keys(this.data), this.data);
|
|
|
+ const { name, id } = this.workflow;
|
|
|
|
|
|
- logs.push({
|
|
|
+ await this.logger.add({
|
|
|
name,
|
|
|
- icon,
|
|
|
status,
|
|
|
message,
|
|
|
id: this.id,
|
|
|
workflowId: id,
|
|
|
- data: jsonData,
|
|
|
- history: this.logs,
|
|
|
- endedAt: this.endedTimestamp,
|
|
|
+ history: this.history,
|
|
|
+ endedAt: endedTimestamp,
|
|
|
+ parentLog: this.parentWorkflow,
|
|
|
startedAt: this.startedTimestamp,
|
|
|
- isChildLog: !!this.parentWorkflow,
|
|
|
- isInCollection: this.isInCollection,
|
|
|
- collectionLogId: this.collectionLogId,
|
|
|
+ data: this.referenceData.dataColumns,
|
|
|
});
|
|
|
-
|
|
|
- await browser.storage.local.set({ logs });
|
|
|
}
|
|
|
|
|
|
this.dispatchEvent('destroyed', {
|
|
|
- id: this.id,
|
|
|
status,
|
|
|
message,
|
|
|
+ id: this.id,
|
|
|
currentBlock: this.currentBlock,
|
|
|
});
|
|
|
|
|
|
+ this.states.off('stop', this.onWorkflowStopped);
|
|
|
+ await this.states.delete(this.id);
|
|
|
+
|
|
|
+ this.isDestroyed = true;
|
|
|
this.eventListeners = {};
|
|
|
- this.tabUpdatedListeners = {};
|
|
|
} catch (error) {
|
|
|
console.error(error);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ async executeBlock(block, prevBlockData) {
|
|
|
+ const currentState = await this.states.get(this.id);
|
|
|
+
|
|
|
+ if (!currentState || currentState.isDestroyed) {
|
|
|
+ if (this.isDestroyed) return;
|
|
|
+
|
|
|
+ await this.destroy('stopped');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.currentBlock = block;
|
|
|
+
|
|
|
+ await this.states.update(this.id, { state: this.state });
|
|
|
+
|
|
|
+ const startExecutedTime = Date.now();
|
|
|
+ const blockHandler = this.blocksHandler[toCamelCase(block?.name)];
|
|
|
+ const handler =
|
|
|
+ !blockHandler && tasks[block.name].category === 'interaction'
|
|
|
+ ? this.blocksHandler.interactionBlock
|
|
|
+ : blockHandler;
|
|
|
+
|
|
|
+ if (!handler) {
|
|
|
+ console.error(`"${block.name}" block doesn't have a handler`);
|
|
|
+ this.destroy('stopped');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const replacedBlock = referenceData({ block, data: this.referenceData });
|
|
|
+ const blockDelay = this.workflow.settings?.blockDelay || 0;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const result = await handler.call(this, replacedBlock, {
|
|
|
+ prevBlockData,
|
|
|
+ refData: this.referenceData,
|
|
|
+ });
|
|
|
+
|
|
|
+ this.addLogHistory({
|
|
|
+ type: 'success',
|
|
|
+ name: block.name,
|
|
|
+ logId: result.logId,
|
|
|
+ duration: Math.round(Date.now() - startExecutedTime),
|
|
|
+ });
|
|
|
+
|
|
|
+ if (result.nextBlockId) {
|
|
|
+ setTimeout(() => {
|
|
|
+ this.executeBlock(this.blocks[result.nextBlockId], result.data);
|
|
|
+ }, blockDelay);
|
|
|
+ } else {
|
|
|
+ this.addLogHistory({
|
|
|
+ type: 'finish',
|
|
|
+ name: 'finish',
|
|
|
+ });
|
|
|
+ this.dispatchEvent('finish');
|
|
|
+ this.destroy('success');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ this.addLogHistory({
|
|
|
+ type: 'error',
|
|
|
+ message: error.message,
|
|
|
+ name: block.name,
|
|
|
+ ...(error.data || {}),
|
|
|
+ });
|
|
|
+
|
|
|
+ if (
|
|
|
+ this.workflow.settings.onError === 'keep-running' &&
|
|
|
+ error.nextBlockId
|
|
|
+ ) {
|
|
|
+ setTimeout(() => {
|
|
|
+ this.executeBlock(this.blocks[error.nextBlockId], error.data || '');
|
|
|
+ }, blockDelay);
|
|
|
+ } else {
|
|
|
+ this.destroy('error', error.message);
|
|
|
+ }
|
|
|
+
|
|
|
+ console.error(`${block.name}:`, error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
dispatchEvent(name, params) {
|
|
|
const listeners = this.eventListeners[name];
|
|
|
|
|
@@ -288,157 +299,88 @@ class WorkflowEngine {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
+ on(name, listener) {
|
|
|
+ (this.eventListeners[name] = this.eventListeners[name] || []).push(
|
|
|
+ listener
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
get state() {
|
|
|
const keys = [
|
|
|
- 'tabId',
|
|
|
- 'isPaused',
|
|
|
- 'isDestroyed',
|
|
|
+ 'history',
|
|
|
+ 'activeTab',
|
|
|
+ 'isUsingProxy',
|
|
|
'currentBlock',
|
|
|
- 'isInCollection',
|
|
|
+ 'referenceData',
|
|
|
+ 'childWorkflowId',
|
|
|
'startedTimestamp',
|
|
|
];
|
|
|
- const state = keys.reduce((acc, key) => {
|
|
|
- acc[key] = this[key];
|
|
|
-
|
|
|
- return acc;
|
|
|
- }, {});
|
|
|
-
|
|
|
- state.name = this.workflow.name;
|
|
|
- state.icon = this.workflow.icon;
|
|
|
+ const state = {
|
|
|
+ name: this.workflow.name,
|
|
|
+ icon: this.workflow.icon,
|
|
|
+ };
|
|
|
|
|
|
- if (this.parentWorkflow) state.parentState = this.parentWorkflow;
|
|
|
+ keys.forEach((key) => {
|
|
|
+ state[key] = this[key];
|
|
|
+ });
|
|
|
|
|
|
return state;
|
|
|
}
|
|
|
|
|
|
- _blockHandler(block, prevBlockData) {
|
|
|
- if (this.isDestroyed) return;
|
|
|
- if (this.isPaused) {
|
|
|
- browser.tabs.get(this.tabId).then(({ status }) => {
|
|
|
- this.isPaused = status !== 'complete';
|
|
|
-
|
|
|
- setTimeout(() => {
|
|
|
- this._blockHandler(block, prevBlockData);
|
|
|
- }, 1000);
|
|
|
+ async _sendMessageToTab(payload, options = {}) {
|
|
|
+ const checkActiveTab = () => {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ const activeTabStatus = () => {
|
|
|
+ browser.tabs
|
|
|
+ .get(this.activeTab.id)
|
|
|
+ .then((tab) => {
|
|
|
+ console.log('Tab status:\t', tab.status);
|
|
|
+ if (tab.status === 'loading') {
|
|
|
+ setTimeout(() => {
|
|
|
+ activeTabStatus();
|
|
|
+ }, 1000);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ resolve();
|
|
|
+ })
|
|
|
+ .catch(reject);
|
|
|
+ };
|
|
|
+
|
|
|
+ activeTabStatus();
|
|
|
});
|
|
|
+ };
|
|
|
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- const disableTimeoutKeys = ['delay', 'javascript-code', 'webhook'];
|
|
|
-
|
|
|
- if (!disableTimeoutKeys.includes(block.name)) {
|
|
|
- this.workflowTimeout = setTimeout(() => {
|
|
|
- if (!this.isDestroyed) this.stop('stop-timeout');
|
|
|
- }, this.workflow.settings.timeout || 120000);
|
|
|
- }
|
|
|
-
|
|
|
- this.currentBlock = block;
|
|
|
-
|
|
|
- workflowState.update(this.id, this.state);
|
|
|
- this.dispatchEvent('update', this.state);
|
|
|
-
|
|
|
- const started = Date.now();
|
|
|
- const blockHandler = this.blocksHandler[toCamelCase(block?.name)];
|
|
|
- const handler =
|
|
|
- !blockHandler && tasks[block.name].category === 'interaction'
|
|
|
- ? this.blocksHandler.interactionBlock
|
|
|
- : blockHandler;
|
|
|
-
|
|
|
- if (handler) {
|
|
|
- const refData = {
|
|
|
- prevBlockData,
|
|
|
- dataColumns: this.data,
|
|
|
- loopData: this.loopData,
|
|
|
- globalData: this.globalData,
|
|
|
- googleSheets: this.googleSheets,
|
|
|
- activeTabUrl: this.activeTabUrl,
|
|
|
- };
|
|
|
- const replacedBlock = referenceData({ block, data: refData });
|
|
|
- const blockDelay =
|
|
|
- block.name === 'trigger' ? 0 : this.workflow.settings?.blockDelay || 0;
|
|
|
-
|
|
|
- handler
|
|
|
- .call(this, replacedBlock, { prevBlockData, refData })
|
|
|
- .then((result) => {
|
|
|
- clearTimeout(this.workflowTimeout);
|
|
|
- this.workflowTimeout = null;
|
|
|
-
|
|
|
- this.addLog({
|
|
|
- type: 'success',
|
|
|
- name: block.name,
|
|
|
- logId: result.logId,
|
|
|
- duration: Math.round(Date.now() - started),
|
|
|
- });
|
|
|
-
|
|
|
- if (result.nextBlockId) {
|
|
|
- setTimeout(() => {
|
|
|
- this._blockHandler(this.blocks[result.nextBlockId], result.data);
|
|
|
- }, blockDelay);
|
|
|
- } else {
|
|
|
- this.addLog({
|
|
|
- type: 'finish',
|
|
|
- name: 'finish',
|
|
|
- });
|
|
|
- this.dispatchEvent('finish');
|
|
|
- this.destroy('success');
|
|
|
- }
|
|
|
- })
|
|
|
- .catch((error) => {
|
|
|
- this.addLog({
|
|
|
- type: 'error',
|
|
|
- message: error.message,
|
|
|
- name: block.name,
|
|
|
- ...(error.data || {}),
|
|
|
- });
|
|
|
-
|
|
|
- if (
|
|
|
- this.workflow.settings.onError === 'keep-running' &&
|
|
|
- error.nextBlockId
|
|
|
- ) {
|
|
|
- setTimeout(() => {
|
|
|
- this._blockHandler(
|
|
|
- this.blocks[error.nextBlockId],
|
|
|
- error.data || ''
|
|
|
- );
|
|
|
- }, blockDelay);
|
|
|
- } else {
|
|
|
- this.destroy('error', error.message);
|
|
|
- }
|
|
|
-
|
|
|
- clearTimeout(this.workflowTimeout);
|
|
|
- this.workflowTimeout = null;
|
|
|
-
|
|
|
- console.error(error);
|
|
|
- });
|
|
|
- } else {
|
|
|
- console.error(`"${block.name}" block doesn't have a handler`);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- _sendMessageToTab(payload, options = {}) {
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- if (!this.tabId) {
|
|
|
+ try {
|
|
|
+ if (!this.activeTab.id) {
|
|
|
const error = new Error('no-tab');
|
|
|
error.workflowId = this.id;
|
|
|
|
|
|
- reject(error);
|
|
|
- return;
|
|
|
+ throw error;
|
|
|
}
|
|
|
|
|
|
- browser.tabs
|
|
|
- .sendMessage(this.tabId, { isBlock: true, ...payload }, options)
|
|
|
- .then(resolve)
|
|
|
- .catch(reject);
|
|
|
- });
|
|
|
- }
|
|
|
+ await checkActiveTab();
|
|
|
|
|
|
- _listener({ id, name, callback, once = true, ...options }) {
|
|
|
- const listenerNames = {
|
|
|
- event: 'eventListener',
|
|
|
- 'tab-updated': 'tabUpdatedListeners',
|
|
|
- };
|
|
|
- this[listenerNames[name]][id] = { callback, once, ...options };
|
|
|
+ this.activeTab.frames = await executeContentScript(
|
|
|
+ this.activeTab.id,
|
|
|
+ options.frameId || 0
|
|
|
+ );
|
|
|
+ const data = await browser.tabs.sendMessage(
|
|
|
+ this.activeTab.id,
|
|
|
+ { isBlock: true, ...payload },
|
|
|
+ options
|
|
|
+ );
|
|
|
+
|
|
|
+ return data;
|
|
|
+ } catch (error) {
|
|
|
+ if (error.message?.startsWith('Could not establish connection')) {
|
|
|
+ error.message = 'Could not establish connection to the active tab';
|
|
|
+ } else if (error.message?.startsWith('No tab')) {
|
|
|
+ error.message = 'active-tab-removed';
|
|
|
+ }
|
|
|
+
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|