Browse Source

feat: update javascript code block

Ahmad Kholid 2 years ago
parent
commit
26adffaf8b

+ 0 - 154
src/content/blocksHandler/handlerJavascriptCode.js

@@ -1,154 +0,0 @@
-import { customAlphabet } from 'nanoid/non-secure';
-import { sendMessage } from '@/utils/message';
-import { automaRefDataStr } from '../utils';
-
-const nanoid = customAlphabet('1234567890abcdef', 5);
-
-function getAutomaScript(refData, everyNewTab) {
-  const varName = `automa${nanoid()}`;
-
-  let str = `
-const ${varName} = ${JSON.stringify(refData)};
-${automaRefDataStr(varName)}
-function automaSetVariable(name, value) {
-  ${varName}.variables[name] = value;
-}
-function automaNextBlock(data, insert = true) {
-  document.body.dispatchEvent(new CustomEvent('__automa-next-block__', { detail: { data, insert, refData: ${varName} } }));
-}
-function automaResetTimeout() {
- document.body.dispatchEvent(new CustomEvent('__automa-reset-timeout__'));
-}
-  `;
-
-  if (everyNewTab) str = automaRefDataStr(varName);
-
-  return str;
-}
-
-function javascriptCode(block) {
-  const automaScript = block.data.everyNewTab
-    ? ''
-    : getAutomaScript(block.refData, block.data.everyNewTab);
-
-  return new Promise((resolve, reject) => {
-    let documentCtx = document;
-
-    if (block.frameSelector) {
-      const iframeCtx = document.querySelector(
-        block.frameSelector
-      )?.contentDocument;
-
-      if (!iframeCtx) {
-        reject(new Error('iframe-not-found'));
-        return;
-      }
-
-      documentCtx = iframeCtx;
-    }
-
-    const scriptAttr = `block--${block.id}`;
-    const isScriptExists = documentCtx.querySelector(
-      `.automa-custom-js[${scriptAttr}]`
-    );
-
-    if (isScriptExists) {
-      resolve('');
-      return;
-    }
-
-    const promisePreloadScripts =
-      block.data?.preloadScripts?.map(async (item) => {
-        try {
-          const { protocol } = new URL(item.src);
-          const isValidUrl = /https?/.test(protocol);
-
-          if (!isValidUrl) return null;
-
-          const script = await sendMessage(
-            'fetch:text',
-            item.src,
-            'background'
-          );
-          const scriptEl = documentCtx.createElement('script');
-
-          scriptEl.type = 'text/javascript';
-          scriptEl.innerHTML = script;
-
-          return {
-            ...item,
-            script: scriptEl,
-          };
-        } catch (error) {
-          return null;
-        }
-      }) || [];
-
-    Promise.allSettled(promisePreloadScripts).then((result) => {
-      const preloadScripts = result.reduce((acc, { status, value }) => {
-        if (status !== 'fulfilled' || !value) return acc;
-
-        acc.push(value);
-        documentCtx.body.appendChild(value.script);
-
-        return acc;
-      }, []);
-
-      const script = document.createElement('script');
-
-      script.setAttribute(scriptAttr, '');
-      script.classList.add('automa-custom-js');
-      script.textContent = `(() => {
-        ${automaScript}
-
-        try {
-          ${block.data.code}
-        } catch (error) {
-          console.error(error);
-          automaNextBlock({ $error: true, message: error.message });
-        }
-      })()`;
-
-      if (!block.data.everyNewTab) {
-        let timeout;
-
-        const cleanUp = (detail = {}) => {
-          script.remove();
-          preloadScripts.forEach((item) => {
-            if (item.removeAfterExec) item.script.remove();
-          });
-
-          clearTimeout(timeout);
-
-          resolve({
-            columns: {
-              data: detail?.data,
-              insert: detail?.insert,
-            },
-            variables: detail?.refData?.variables,
-          });
-        };
-
-        documentCtx.body.addEventListener(
-          '__automa-next-block__',
-          ({ detail }) => {
-            cleanUp(detail || {});
-          }
-        );
-        documentCtx.body.addEventListener('__automa-reset-timeout__', () => {
-          clearTimeout(timeout);
-
-          timeout = setTimeout(cleanUp, block.data.timeout);
-        });
-
-        timeout = setTimeout(cleanUp, block.data.timeout);
-      } else {
-        resolve();
-      }
-
-      documentCtx.documentElement.appendChild(script);
-    });
-  });
-}
-
-export default javascriptCode;

+ 2 - 0
src/content/blocksHandler/handlerTakeScreenshot.js

@@ -156,6 +156,8 @@ export default async function ({ tabId, options, data: { type, selector } }) {
       else if (position === 'fixed') el.setAttribute('is-fixed', '');
     });
 
+  scrollableElement.scrollTo(0, 0);
+
   let scaleDiff = 1;
   let scrollPosition = 0;
   let canvasAdjusted = false;

+ 36 - 35
src/newtab/utils/workflowEngine/WorkflowEngine.js

@@ -356,39 +356,6 @@ class WorkflowEngine {
       this.workers.clear();
       this.executeQueue();
 
-      if (!this.workflow.isTesting) {
-        const { name, id, teamId } = this.workflow;
-
-        await this.logger.add({
-          detail: {
-            name,
-            status,
-            teamId,
-            message,
-            id: this.id,
-            workflowId: id,
-            endedAt: endedTimestamp,
-            parentLog: this.parentWorkflow,
-            startedAt: this.startedTimestamp,
-          },
-          history: {
-            logId: this.id,
-            data: this.saveLog ? this.history : [],
-          },
-          ctxData: {
-            logId: this.id,
-            data: this.historyCtxData,
-          },
-          data: {
-            logId: this.id,
-            data: {
-              table: [...this.referenceData.table],
-              variables: { ...this.referenceData.variables },
-            },
-          },
-        });
-      }
-
       this.states.off('stop', this.onWorkflowStopped);
       await this.states.delete(this.id);
 
@@ -457,6 +424,42 @@ class WorkflowEngine {
         }
       );
 
+      if (!this.workflow.isTesting) {
+        const { name, id, teamId } = this.workflow;
+
+        await this.logger.add({
+          detail: {
+            name,
+            status,
+            teamId,
+            message,
+            id: this.id,
+            workflowId: id,
+            endedAt: endedTimestamp,
+            parentLog: this.parentWorkflow,
+            startedAt: this.startedTimestamp,
+          },
+          history: {
+            logId: this.id,
+            data: this.saveLog ? this.history : [],
+          },
+          ctxData: {
+            logId: this.id,
+            data: this.historyCtxData,
+          },
+          data: {
+            logId: this.id,
+            data: {
+              table: [...this.referenceData.table],
+              variables: { ...this.referenceData.variables },
+            },
+          },
+        });
+      }
+    } catch (error) {
+      console.error(error);
+    } finally {
+      this.workflow = null;
       this.isDestroyed = true;
       this.referenceData = null;
       this.eventListeners = null;
@@ -469,8 +472,6 @@ class WorkflowEngine {
       this.columnsId = null;
       this.historyCtxData = null;
       this.preloadScripts = null;
-    } catch (error) {
-      console.error(error);
     }
   }
 

+ 9 - 2
src/newtab/utils/workflowEngine/blocksHandler/handlerActiveTab.js

@@ -1,6 +1,6 @@
 import browser from 'webextension-polyfill';
 import { sleep } from '@/utils/helper';
-import { attachDebugger } from '../helper';
+import { attachDebugger, injectPreloadScript } from '../helper';
 
 async function activeTab(block) {
   try {
@@ -42,7 +42,14 @@ async function activeTab(block) {
 
     if (this.preloadScripts.length > 0) {
       const preloadScripts = this.preloadScripts.map((script) =>
-        this._sendMessageToTab(script)
+        injectPreloadScript({
+          script,
+          frameSelector: this.frameSelector,
+          target: {
+            tabId: this.activeTab.id,
+            frameIds: [this.activeTab.frameId || 0],
+          },
+        })
       );
       await Promise.allSettled(preloadScripts);
     }

+ 201 - 19
src/newtab/utils/workflowEngine/blocksHandler/handlerJavascriptCode.js

@@ -1,34 +1,216 @@
+import { customAlphabet } from 'nanoid/non-secure';
+import browser from 'webextension-polyfill';
+import { automaRefDataStr, waitTabLoaded } from '../helper';
+
+const nanoid = customAlphabet('1234567890abcdef', 5);
+
+function getAutomaScript(refData, everyNewTab) {
+  const varName = `automa${nanoid()}`;
+
+  let str = `
+const ${varName} = ${JSON.stringify(refData)};
+${automaRefDataStr(varName)}
+function automaSetVariable(name, value) {
+  ${varName}.variables[name] = value;
+}
+function automaNextBlock(data, insert = true) {
+  document.body.dispatchEvent(new CustomEvent('__automa-next-block__', { detail: { data, insert, refData: ${varName} } }));
+}
+function automaResetTimeout() {
+ document.body.dispatchEvent(new CustomEvent('__automa-reset-timeout__'));
+}
+  `;
+
+  if (everyNewTab) str = automaRefDataStr(varName);
+
+  return str;
+}
+
 export async function javascriptCode({ outputs, data, ...block }, { refData }) {
   const nextBlockId = this.getBlockConnections(block.id);
 
   if (data.everyNewTab) {
-    const isScriptExist = this.preloadScripts.find(({ id }) => id === block.id);
-
-    if (!isScriptExist) {
-      this.preloadScripts.push({ ...block, data });
-    }
-  }
-  if (!this.activeTab.id) {
-    if (!data.everyNewTab) {
-      throw new Error('no-tab');
-    } else {
-      return { data: '', nextBlockId };
-    }
+    const isScriptExist = this.preloadScripts.some(({ id }) => id === block.id);
+    if (!isScriptExist) this.preloadScripts.push({ ...block, data });
+    if (!this.activeTab.id) return { data: '', nextBlockId };
+  } else if (!this.activeTab.id) {
+    throw new Error('no-tab');
   }
 
-  const payload = { ...block, data, refData: { variables: {} } };
+  const payload = {
+    ...block,
+    data,
+    refData: { variables: {} },
+    frameSelector: this.frameSelector,
+  };
   if (data.code.includes('automaRefData')) {
     payload.refData = { ...refData, secrets: {} };
   }
 
-  if (!data.code.includes('automaNextBlock'))
-    payload.data.code += `\nautomaNextBlock()`;
+  const preloadScriptsPromise = await Promise.allSettled(
+    data.preloadScripts.map(async (script) => {
+      const { protocol } = new URL(script.src);
+      const isValidUrl = /https?/.test(protocol);
+      if (!isValidUrl) return null;
+
+      const response = await fetch(script.src);
+      if (!response.ok) throw new Error(response.statusText);
 
-  const result = await this._sendMessageToTab(
-    payload,
-    {},
-    data.runBeforeLoad ?? false
+      const result = await response.text();
+
+      return {
+        script: result,
+        id: `automa-script-${nanoid()}`,
+        removeAfterExec: script.removeAfterExec,
+      };
+    })
   );
+  const preloadScripts = preloadScriptsPromise.reduce((acc, item) => {
+    if (item.status === 'fulfilled') acc.push(item.value);
+
+    return acc;
+  }, []);
+
+  const automaScript = data.everyNewTab
+    ? ''
+    : getAutomaScript(payload.refData, data.everyNewTab);
+
+  await waitTabLoaded({
+    tabId: this.activeTab.id,
+    ms: this.settings?.tabLoadTimeout ?? 30000,
+  });
+
+  const [{ result }] = await browser.scripting.executeScript({
+    world: 'MAIN',
+    args: [payload, preloadScripts, automaScript],
+    target: {
+      tabId: this.activeTab.id,
+      frameIds: [this.activeTab.frameId || 0],
+    },
+    func: ($blockData, $preloadScripts, $automaScript) => {
+      return new Promise((resolve, reject) => {
+        try {
+          let $documentCtx = document;
+
+          if ($blockData.frameSelector) {
+            const iframeCtx = document.querySelector(
+              $blockData.frameSelector
+            )?.contentDocument;
+
+            if (!iframeCtx) {
+              reject(new Error('iframe-not-found'));
+              return;
+            }
+
+            $documentCtx = iframeCtx;
+          }
+
+          const scriptAttr = `block--${$blockData.id}`;
+
+          const isScriptExists = $documentCtx.querySelector(
+            `.automa-custom-js[${scriptAttr}]`
+          );
+          if (isScriptExists) {
+            resolve('');
+            return;
+          }
+
+          const script = document.createElement('script');
+          script.setAttribute(scriptAttr, '');
+          script.classList.add('automa-custom-js');
+          script.textContent = `(() => {
+            ${$automaScript}
+
+            try {
+              ${$blockData.data.code}
+              ${
+                $blockData.data.everyNewTab ||
+                $blockData.data.code.includes('automaNextBlock')
+                  ? ''
+                  : 'automaNextBlock()'
+              }
+            } catch (error) {
+              console.error(error);
+              ${
+                $blockData.data.everyNewTab
+                  ? ''
+                  : 'automaNextBlock({ $error: true, message: error.message })'
+              }
+            }
+          })()`;
+
+          const preloadScriptsEl = $preloadScripts.map((item) => {
+            const scriptEl = document.createElement('script');
+            scriptEl.id = item.id;
+            scriptEl.textContent = item.script;
+
+            $documentCtx.head.appendChild(scriptEl);
+
+            return { element: scriptEl, removeAfterExec: item.removeAfterExec };
+          });
+
+          if (!$blockData.data.everyNewTab) {
+            let timeout;
+            let onNextBlock;
+            let onResetTimeout;
+
+            /* eslint-disable-next-line */
+            function cleanUp(detail) {
+              script.remove();
+              preloadScriptsEl.forEach((item) => {
+                if (item.removeAfterExec) item.script.remove();
+              });
+
+              clearTimeout(timeout);
+
+              $documentCtx.body.removeEventListener(
+                '__automa-reset-timeout__',
+                onResetTimeout
+              );
+              $documentCtx.body.removeEventListener(
+                '__automa-next-block__',
+                onNextBlock
+              );
+
+              resolve({
+                columns: {
+                  data: detail?.data,
+                  insert: detail?.insert,
+                },
+                variables: detail?.refData?.variables,
+              });
+            }
+
+            onNextBlock = ({ detail }) => {
+              cleanUp(detail || {});
+            };
+            onResetTimeout = () => {
+              clearTimeout(timeout);
+              timeout = setTimeout(cleanUp, $blockData.data.timeout);
+            };
+
+            $documentCtx.body.addEventListener(
+              '__automa-next-block__',
+              onNextBlock
+            );
+            $documentCtx.body.addEventListener(
+              '__automa-reset-timeout__',
+              onResetTimeout
+            );
+
+            timeout = setTimeout(cleanUp, $blockData.data.timeout);
+          } else {
+            resolve();
+          }
+
+          $documentCtx.head.appendChild(script);
+        } catch (error) {
+          console.error(error);
+        }
+      });
+    },
+  });
+
   if (result) {
     if (result.columns.data?.$error) {
       throw new Error(result.columns.data.message);

+ 14 - 2
src/newtab/utils/workflowEngine/blocksHandler/handlerNewTab.js

@@ -1,6 +1,11 @@
 import browser from 'webextension-polyfill';
 import { isWhitespace, sleep } from '@/utils/helper';
-import { waitTabLoaded, attachDebugger, sendDebugCommand } from '../helper';
+import {
+  waitTabLoaded,
+  attachDebugger,
+  sendDebugCommand,
+  injectPreloadScript,
+} from '../helper';
 
 async function newTab({ id, data }) {
   if (this.windowId) {
@@ -84,7 +89,14 @@ async function newTab({ id, data }) {
 
   if (this.preloadScripts.length > 0) {
     const preloadScripts = this.preloadScripts.map((script) =>
-      this._sendMessageToTab(script, {}, true)
+      injectPreloadScript({
+        script,
+        frameSelector: this.frameSelector,
+        target: {
+          tabId: this.activeTab.id,
+          frameIds: [this.activeTab.frameId || 0],
+        },
+      })
     );
     await Promise.allSettled(preloadScripts);
   }

+ 9 - 2
src/newtab/utils/workflowEngine/blocksHandler/handlerSwitchTab.js

@@ -1,5 +1,5 @@
 import browser from 'webextension-polyfill';
-import { attachDebugger } from '../helper';
+import { attachDebugger, injectPreloadScript } from '../helper';
 
 export default async function ({ data, id }) {
   const nextBlockId = this.getBlockConnections(id);
@@ -80,7 +80,14 @@ export default async function ({ data, id }) {
 
   if (this.preloadScripts.length > 0) {
     const preloadScripts = this.preloadScripts.map((script) =>
-      this._sendMessageToTab(script, {}, true)
+      injectPreloadScript({
+        script,
+        frameSelector: this.frameSelector,
+        target: {
+          tabId: this.activeTab.id,
+          frameIds: [this.activeTab.frameId || 0],
+        },
+      })
     );
     await Promise.allSettled(preloadScripts);
   }

+ 0 - 1
src/newtab/utils/workflowEngine/blocksHandler/handlerTakeScreenshot.js

@@ -65,7 +65,6 @@ async function takeScreenshot({ data, id, label }) {
         let result = null;
 
         if (isChrome) {
-          console.log(tab);
           result = await browser.tabs.captureVisibleTab(tab.windowId, options);
         } else {
           result = await browser.tabs.captureTab(this.activeTab.id, options);

+ 68 - 0
src/newtab/utils/workflowEngine/helper.js

@@ -120,3 +120,71 @@ export function convertData(data, type) {
 
   return result;
 }
+
+export function automaRefDataStr(varName) {
+  return `
+function findData(obj, path) {
+  const paths = path.split('.');
+  const isWhitespace = paths.length === 1 && !/\\S/.test(paths[0]);
+
+  if (path.startsWith('$last') && Array.isArray(obj)) {
+    paths[0] = obj.length - 1;
+  }
+
+  if (paths.length === 0 || isWhitespace) return obj;
+  else if (paths.length === 1) return obj[paths[0]];
+
+  let result = obj;
+
+  for (let i = 0; i < paths.length; i++) {
+    if (result[paths[i]] == undefined) {
+      return undefined;
+    } else {
+      result = result[paths[i]];
+    }
+  }
+
+  return result;
+}
+function automaRefData(keyword, path = '') {
+  const data = ${varName}[keyword];
+
+  if (!data) return;
+
+  return findData(data, path);
+}
+  `;
+}
+
+export function injectPreloadScript({ target, script, frameSelector }) {
+  return browser.scripting.executeScript({
+    target,
+    world: 'MAIN',
+    args: [script.id, script.data.code, frameSelector || null],
+    func: (scriptId, code, frame) => {
+      let $documentCtx = document;
+
+      if (frame) {
+        const iframeCtx = document.querySelector(frame)?.contentDocument;
+        if (!iframeCtx) return;
+
+        $documentCtx = iframeCtx;
+      }
+
+      const scriptAttr = `block--${scriptId}`;
+
+      const isScriptExists = $documentCtx.querySelector(
+        `.automa-custom-js[${scriptAttr}]`
+      );
+
+      if (isScriptExists) return;
+
+      const scriptEl = $documentCtx.createElement('script');
+      scriptEl.textContent = code;
+      scriptEl.setAttribute(scriptAttr, '');
+      scriptEl.classList.add('automa-custom-js');
+
+      $documentCtx.documentElement.appendChild(scriptEl);
+    },
+  });
+}