Browse Source

refactor: workflow engine

Ahmad Kholid 3 years ago
parent
commit
5f32882227

+ 4 - 0
src/background/index.js

@@ -101,6 +101,10 @@ chrome.runtime.onInstalled.addListener((details) => {
 
 const message = new MessageListener('background');
 
+message.on('get:sender', (_, sender) => {
+  return sender;
+});
+
 message.on('collection:execute', executeCollection);
 message.on('collection:stop', (id) => {
   const collection = runningCollections[id];

+ 73 - 90
src/background/workflow-engine/blocks-handler.js

@@ -70,8 +70,6 @@ export async function trigger(block) {
       await browser.tabs.executeScript(this.tabId, {
         file: './contentScript.bundle.js',
       });
-
-      this._connectTab(this.tabId);
     }
 
     return { nextBlockId, data: '' };
@@ -202,7 +200,6 @@ function tabUpdatedListener(tab) {
           });
 
           deleteListener();
-          this._connectTab(tabId);
 
           resolve();
         } catch (error) {
@@ -264,7 +261,6 @@ export async function activeTab(block) {
 
     this.tabId = tab.id;
     this.windowId = tab.windowId;
-    this._connectTab(tab.id);
 
     return data;
   } catch (error) {
@@ -342,87 +338,80 @@ export async function takeScreenshot(block) {
   }
 }
 
-export function interactionHandler(block) {
-  return new Promise((resolve, reject) => {
-    const nextBlockId = getBlockConnection(block);
+export async function interactionHandler(block) {
+  const nextBlockId = getBlockConnection(block);
 
-    if (!this.connectedTab) {
-      reject(generateBlockError(block));
+  try {
+    const data = await this._sendMessageToTab(block);
 
-      return;
+    if (block.name === 'link')
+      await new Promise((resolve) => setTimeout(resolve, 5000));
+
+    if (data?.isError) {
+      const error = new Error(data.message);
+      error.nextBlockId = nextBlockId;
+
+      throw error;
     }
 
-    this.connectedTab.postMessage({ isBlock: true, ...block });
-    this._listener({
-      name: 'tab-message',
-      id: block.name,
-      once: true,
-      delay: block.name === 'link' ? 5000 : 0,
-      callback: (data) => {
-        if (data?.isError) {
-          const error = new Error(data.message);
-          error.nextBlockId = nextBlockId;
+    const getColumn = (name) =>
+      this.workflow.dataColumns.find((item) => item.name === name) || {
+        name: 'column',
+        type: 'text',
+      };
+    const pushData = (column, value) => {
+      this.data[column.name]?.push(convertData(value, column.type));
+    };
 
-          reject(error);
-          return;
-        }
+    if (objectHasKey(block.data, 'dataColumn')) {
+      const column = getColumn(block.data.dataColumn);
 
-        const getColumn = (name) =>
-          this.workflow.dataColumns.find((item) => item.name === name) || {
-            name: 'column',
-            type: 'text',
-          };
-        const pushData = (column, value) => {
-          this.data[column.name]?.push(convertData(value, column.type));
-        };
-
-        if (objectHasKey(block.data, 'dataColumn')) {
-          const column = getColumn(block.data.dataColumn);
-
-          if (block.data.saveData) {
-            if (Array.isArray(data)) {
-              data.forEach((item) => {
-                pushData(column, item);
-              });
-            } else {
-              pushData(column, data);
-            }
-          }
-        } else if (block.name === 'javascript-code') {
-          const memoColumn = {};
-          const pushObjectData = (obj) => {
-            Object.entries(obj).forEach(([key, value]) => {
-              let column;
-
-              if (memoColumn[key]) {
-                column = memoColumn[key];
-              } else {
-                const currentColumn = getColumn(key);
-
-                column = currentColumn;
-                memoColumn[key] = currentColumn;
-              }
-
-              pushData(column, value);
-            });
-          };
-
-          if (Array.isArray(data)) {
-            data.forEach((obj) => {
-              if (isObject(obj)) pushObjectData(obj);
-            });
-          } else if (isObject(data)) {
-            pushObjectData(data);
-          }
+      if (block.data.saveData) {
+        if (Array.isArray(data)) {
+          data.forEach((item) => {
+            pushData(column, item);
+          });
+        } else {
+          pushData(column, data);
         }
+      }
+    } else if (block.name === 'javascript-code') {
+      const memoColumn = {};
+      const pushObjectData = (obj) => {
+        Object.entries(obj).forEach(([key, value]) => {
+          let column;
+
+          if (memoColumn[key]) {
+            column = memoColumn[key];
+          } else {
+            const currentColumn = getColumn(key);
+
+            column = currentColumn;
+            memoColumn[key] = currentColumn;
+          }
 
-        resolve({
-          data,
-          nextBlockId,
+          pushData(column, value);
         });
-      },
-    });
-  });
+      };
+
+      if (Array.isArray(data)) {
+        data.forEach((obj) => {
+          if (isObject(obj)) pushObjectData(obj);
+        });
+      } else if (isObject(data)) {
+        pushObjectData(data);
+      }
+    }
+
+    return {
+      data,
+      nextBlockId,
+    };
+  } catch (error) {
+    error.nextBlockId = nextBlockId;
+
+    throw error;
+  }
 }
 
 export function delay(block) {
@@ -449,24 +438,18 @@ export function exportData(block) {
 
 export function elementExists(block) {
   return new Promise((resolve, reject) => {
-    if (!this.connectedTab) {
-      reject(generateBlockError(block));
-
-      return;
-    }
-
-    this.connectedTab.postMessage({ isBlock: true, ...block });
-    this._listener({
-      name: 'tab-message',
-      id: block.name,
-      once: true,
-      callback: (data) => {
+    this._sendMessageToTab(block)
+      .then((data) => {
         resolve({
           data,
           nextBlockId: getBlockConnection(block, data ? 1 : 2),
         });
-      },
-    });
+      })
+      .catch((error) => {
+        error.nextBlockId = getBlockConnection(block);
+
+        reject(error);
+      });
   });
 }
 

+ 14 - 35
src/background/workflow-engine/index.js

@@ -3,31 +3,16 @@ import browser from 'webextension-polyfill';
 import { nanoid } from 'nanoid';
 import { toCamelCase } from '@/utils/helper';
 import { tasks } from '@/utils/shared';
-import referenceData from '@/utils/reference-data';
 import errorMessage from './error-message';
+import referenceData from '@/utils/reference-data';
 import workflowState from '../workflow-state';
 import * as blocksHandler from './blocks-handler';
 
 let reloadTimeout;
 
-function tabMessageHandler({ type, data }) {
-  const listener = this.tabMessageListeners[type];
-
-  if (listener) {
-    setTimeout(() => {
-      listener.callback(data);
-    }, listener.delay || 0);
-
-    if (listener.once) delete this.tabMessageListeners[type];
-  }
-}
 function tabRemovedHandler(tabId) {
   if (tabId !== this.tabId) return;
 
-  this.connectedTab?.onMessage.removeListener(this.tabMessageHandler);
-  this.connectedTab?.disconnect();
-
-  delete this.connectedTab;
   delete this.tabId;
 
   if (tasks[this.currentBlock.name].category === 'interaction') {
@@ -61,7 +46,7 @@ function tabUpdatedHandler(tabId, changeInfo) {
           file: './contentScript.bundle.js',
         })
         .then(() => {
-          if (this.connectedTab) this._connectTab(this.tabId);
+          this.tabId = tabId;
 
           this.isPaused = false;
         })
@@ -82,6 +67,7 @@ class WorkflowEngine {
     this.collectionLogId = collectionLogId;
     this.data = {};
     this.blocks = {};
+    this.frames = {};
     this.eventListeners = {};
     this.repeatedTasks = {};
     this.loopList = {};
@@ -93,9 +79,7 @@ class WorkflowEngine {
     this.workflowTimeout = null;
     this.windowId = null;
 
-    this.tabMessageListeners = {};
     this.tabUpdatedListeners = {};
-    this.tabMessageHandler = tabMessageHandler.bind(this);
     this.tabUpdatedHandler = tabUpdatedHandler.bind(this);
     this.tabRemovedHandler = tabRemovedHandler.bind(this);
   }
@@ -176,7 +160,6 @@ class WorkflowEngine {
       this.dispatchEvent('destroyed', { id: this.id, status, message });
 
       this.eventListeners = {};
-      this.tabMessageListeners = {};
       this.tabUpdatedListeners = {};
 
       await browser.tabs.onRemoved.removeListener(this.tabRemovedHandler);
@@ -335,29 +318,25 @@ class WorkflowEngine {
     }
   }
 
-  _connectTab(tabId) {
-    const connectedTab = browser.tabs.connect(tabId, {
-      name: `${this.workflow.id}--${this.workflow.name.slice(0, 10)}`,
-    });
-
-    if (this.connectedTab) {
-      this.connectedTab.onMessage.removeListener(this.tabMessageHandler);
-      this.connectedTab.disconnect();
-    }
-
-    connectedTab.onMessage.addListener(this.tabMessageHandler);
+  _sendMessageToTab(block, options = {}) {
+    return new Promise((resolve, reject) => {
+      if (!this.tabId) {
+        const message = errorMessage('no-tab', tasks[block.name]);
 
-    this.connectedTab = connectedTab;
-    this.tabId = tabId;
+        reject(new Error(message));
+      }
 
-    return connectedTab;
+      browser.tabs
+        .sendMessage(this.tabId, { isBlock: true, ...block }, options)
+        .then(resolve)
+        .catch(reject);
+    });
   }
 
   _listener({ id, name, callback, once = true, ...options }) {
     const listenerNames = {
       event: 'eventListener',
       'tab-updated': 'tabUpdatedListeners',
-      'tab-message': 'tabMessageListeners',
     };
     this[listenerNames[name]][id] = { callback, once, ...options };
 

+ 8 - 1
src/content/blocks-handler.js

@@ -36,6 +36,13 @@ function handleElement({ data, id }, callback) {
   }
 }
 
+export function switchTo(block) {
+  return new Promise((resolve) => {
+    console.log(block);
+    resolve('');
+  });
+}
+
 export function eventClick(block) {
   return new Promise((resolve) => {
     handleElement(block, (element) => {
@@ -183,7 +190,7 @@ export function forms(block) {
         resolve('');
       });
     } else if (elements) {
-      markElement(element, block);
+      markElement(elements, block);
       handleFormElement(elements, data, resolve);
     } else {
       resolve('');

+ 80 - 35
src/content/element-selector/ElementSelector.ce.vue

@@ -1,23 +1,33 @@
 <template>
-  <div
-    :style="{
-      transform: `translate(${element.hovered.x}px, ${element.hovered.y}px)`,
-      height: element.hovered.height + 'px',
-      width: element.hovered.width + 'px',
-    }"
-    class="indicator pointer-events-auto"
-  ></div>
-  <div
-    v-if="element.selector"
-    :style="{
-      transform: `translate(${element.selected.x}px, ${element.selected.y}px)`,
-      height: element.selected.height + 'px',
-      width: element.selected.width + 'px',
-      zIndex: 99,
-    }"
-    class="indicator selected"
-  ></div>
+  <template v-if="!element.hide">
+    <div class="overlay"></div>
+    <div
+      :style="{
+        transform: `translate(${element.hovered.x}px, ${element.hovered.y}px)`,
+        height: element.hovered.height + 'px',
+        width: element.hovered.width + 'px',
+      }"
+      class="indicator pointer-events-auto"
+    ></div>
+    <div
+      v-if="element.selector"
+      :style="{
+        transform: `translate(${element.selected.x}px, ${element.selected.y}px)`,
+        height: element.selected.height + 'px',
+        width: element.selected.width + 'px',
+        zIndex: 99,
+      }"
+      class="indicator selected"
+    ></div>
+  </template>
   <div class="card">
+    <button
+      title="Toggle hide"
+      class="mr-2"
+      @click="element.hide = !element.hide"
+    >
+      <v-remix-icon :path="element.hide ? riEyeLine : riEyeOffLine" />
+    </button>
     <div class="selector">
       <v-remix-icon
         style="cursor: pointer"
@@ -33,32 +43,40 @@
         :value="element.selector"
       />
     </div>
-    <template v-if="element.selector">
+    <template v-if="element.selector && !element.hide">
       <button
         title="Select parent element (press P)"
+        class="ml-2"
         @click="selectParentElement"
       >
         <v-remix-icon :path="riArrowDownLine" rotate="180" />
       </button>
       <button
         title="Select parent element (press C)"
+        class="ml-2"
         @click="selectChildElement"
       >
         <v-remix-icon :path="riArrowDownLine" />
       </button>
     </template>
-    <button class="primary" @click="destroy">Close</button>
+    <button class="primary ml-2" @click="destroy">Close</button>
   </div>
 </template>
 <script setup>
 import { reactive } from 'vue';
 import { finder } from '@medv/finder';
 import { VRemixIcon } from 'v-remixicon';
-import { riFileCopyLine, riArrowDownLine } from 'v-remixicon/icons';
+import {
+  riFileCopyLine,
+  riArrowDownLine,
+  riEyeLine,
+  riEyeOffLine,
+} from 'v-remixicon/icons';
 
 /* to-do get list of attribute value */
 
 const element = reactive({
+  hide: window.self !== window.top,
   hovered: {},
   selected: {},
   selector: '',
@@ -83,24 +101,22 @@ function getElementRect(target) {
   };
 }
 function handleMouseMove({ target }) {
-  if (targetEl === target || target === root) return;
+  if (element.hide || targetEl === target || target === root) return;
 
   targetEl = target;
 
   element.hovered = getElementRect(target);
 }
 function copySelector() {
-  navigator.clipboard
-    .writeText(element.selector)
-    .then(() => {
-      root.shadowRoot.querySelector('input')?.select();
-    })
-    .catch((error) => {
-      console.error(error);
-    });
+  root.shadowRoot.querySelector('input')?.select();
+
+  navigator.clipboard.writeText(element.selector).catch((error) => {
+    document.execCommand('copy');
+    console.error(error);
+  });
 }
 function selectChildElement() {
-  if (selectedPath.length === 0) return;
+  if (selectedPath.length === 0 || element.hide) return;
 
   const currentEl = selectedPath[pathIndex];
   let activeEl = currentEl;
@@ -124,7 +140,12 @@ function selectChildElement() {
   selectedEl = activeEl;
 }
 function selectParentElement() {
-  if (selectedEl.tagName === 'HTML' || selectedPath.length === 0) return;
+  if (
+    selectedEl.tagName === 'HTML' ||
+    selectedPath.length === 0 ||
+    element.hide
+  )
+    return;
 
   pathIndex += 1;
   const activeEl = selectedPath[pathIndex];
@@ -134,7 +155,7 @@ function selectParentElement() {
   selectedEl = activeEl;
 }
 function handleClick(event) {
-  if (event.target === root) return;
+  if (event.target === root || element.hide) return;
 
   event.preventDefault();
   event.stopPropagation();
@@ -177,6 +198,12 @@ window.addEventListener('keyup', handleKeyup);
 window.addEventListener('scroll', handleScroll);
 document.addEventListener('click', handleClick, true);
 window.addEventListener('mousemove', handleMouseMove);
+
+// chrome.runtime.sendMessage({
+//   name: 'background--get:sender',
+// }, (result) => {
+//   console.log(result, 'exe');
+// });
 </script>
 <style>
 :host {
@@ -185,7 +212,6 @@ window.addEventListener('mousemove', handleMouseMove);
   width: 100%;
   top: 0;
   left: 0;
-  background-color: rgba(0, 0, 0, 0.2);
   pointer-events: none;
   z-index: 99999;
   color: #18181b;
@@ -215,7 +241,6 @@ button {
   display: flex;
   align-items: center;
   justify-content: center;
-  margin-left: 6px;
   cursor: pointer;
 }
 button.primary {
@@ -230,6 +255,25 @@ button.primary {
   padding-left: 12px;
   background-color: #e4e4e7;
 }
+
+.ml-2 {
+  margin-left: 6px;
+}
+
+.mr-2 {
+  margin-right: 6px;
+}
+
+.overlay {
+  background-color: rgba(0, 0, 0, 0.2);
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  pointer-events: none;
+}
+
 input {
   border: none;
   color: inherit;
@@ -251,6 +295,7 @@ input:focus {
   border-radius: 8px;
   padding: 12px;
   color: #1f2937;
+  border: 1px solid #e4e4e7;
   pointer-events: all;
   z-index: 999;
 }

+ 12 - 27
src/content/index.js

@@ -3,39 +3,24 @@ import { toCamelCase } from '@/utils/helper';
 import elementSelector from './element-selector';
 import * as blocksHandler from './blocks-handler';
 
-function onConnectListener() {
-  browser.runtime.onConnect.addListener((port) => {
-    port.onMessage.addListener((data) => {
-      const handler = blocksHandler[toCamelCase(data.name)];
+browser.runtime.onMessage.addListener((data) => {
+  if (data.isBlock) {
+    const handler = blocksHandler[toCamelCase(data.name)];
 
-      if (handler) {
-        handler(data)
-          .then((result) => {
-            port.postMessage({ type: data.name, data: result });
-          })
-          .catch((error) => {
-            port.postMessage({
-              isError: true,
-              message: error?.message || error,
-            });
-          });
-      } else {
-        console.error(`"${data.name}" doesn't have a handler`);
-      }
-    });
-  });
-}
+    if (handler) {
+      return handler(data);
+    }
+    console.error(`"${data.name}" doesn't have a handler`);
+
+    return Promise.resolve('');
+  }
 
-browser.runtime.onMessage.addListener(({ type }) => {
   return new Promise((resolve) => {
-    if (type === 'content-script-exists') {
+    if (data.type === 'content-script-exists') {
       resolve(true);
-    } else if (type === 'select-element') {
+    } else if (data.type === 'select-element') {
       elementSelector();
       resolve(true);
     }
   });
 });
-
-if (document.readyState === 'complete') onConnectListener();
-else window.addEventListener('load', onConnectListener);

+ 2 - 0
src/content/shortcut.js

@@ -17,6 +17,8 @@ function getTriggerBlock(workflow) {
   return trigger;
 }
 
+window.__halo = 'halo';
+
 (async () => {
   try {
     const { shortcuts, workflows } = await browser.storage.local.get([

+ 2 - 0
src/lib/v-remixicon.js

@@ -2,6 +2,7 @@ import vRemixicon from 'v-remixicon';
 import {
   riHome5Line,
   riFolderLine,
+  riArrowUpDownLine,
   riRefreshLine,
   riBookOpenLine,
   riGithubFill,
@@ -70,6 +71,7 @@ import {
 export const icons = {
   riHome5Line,
   riFolderLine,
+  riArrowUpDownLine,
   riRefreshLine,
   riBookOpenLine,
   riGithubFill,

+ 1 - 0
src/popup/pages/Home.vue

@@ -132,6 +132,7 @@ async function selectElement() {
   } catch (error) {
     if (error.message.includes('Could not establish connection.')) {
       await browser.tabs.executeScript(tab.id, {
+        allFrames: true,
         file: './contentScript.bundle.js',
       });
 

+ 0 - 0
src/utils/keyboard-shortcut.js


+ 16 - 0
src/utils/shared.js

@@ -421,6 +421,22 @@ export const tasks = {
       loopId: '',
     },
   },
+  'switch-to': {
+    name: 'Switch to',
+    description: 'Switch between main window and iframe',
+    icon: 'riArrowUpDownLine',
+    component: 'BlockBasic',
+    editComponent: 'EditSwitchTo',
+    category: 'interaction',
+    inputs: 1,
+    outputs: 1,
+    allowedInputs: true,
+    maxConnection: 1,
+    data: {
+      selector: '',
+      windowType: 'main-window',
+    },
+  },
 };
 
 export const categories = {