Browse Source

feat: add execution context in conditions block

Ahmad Kholid 2 years ago
parent
commit
2acefe0df3

+ 33 - 6
src/components/newtab/shared/SharedConditionBuilder/ConditionBuilderInputs.vue

@@ -23,12 +23,31 @@
           </option>
         </optgroup>
       </ui-select>
-      <template v-for="(_, name) in item.data" :key="item.id + name">
-        <v-remixicon
-          v-if="name === 'code'"
-          :title="t('workflow.conditionBuilder.topAwait')"
-          name="riInformationLine"
-        />
+      <template
+        v-for="name in getConditionDataList(item)"
+        :key="item.id + name"
+      >
+        <template v-if="name === 'code'">
+          <ui-select
+            v-model="inputsData[index].data.context"
+            :placeholder="t('workflow.blocks.javascript-code.context.name')"
+            class="mr-2"
+          >
+            <option
+              v-for="context in ['website', 'background']"
+              :key="context"
+              :value="context"
+            >
+              {{
+                t(`workflow.blocks.javascript-code.context.items.${context}`)
+              }}
+            </option>
+          </ui-select>
+          <v-remixicon
+            :title="t('workflow.conditionBuilder.topAwait')"
+            name="riInformationLine"
+          />
+        </template>
         <edit-autocomplete
           :disabled="name === 'code'"
           :class="[name === 'code' ? 'w-full' : 'flex-1']"
@@ -121,9 +140,17 @@ const conditionOperators = conditionBuilder.compareTypes.reduce((acc, type) => {
   return acc;
 }, {});
 
+const excludeData = ['context'];
+
 const { t } = useI18n();
 const inputsData = ref(cloneDeep(props.data));
 
+function getConditionDataList(inputData) {
+  const keys = Object.keys(inputData.data);
+  const filteredKeys = keys.filter((item) => !excludeData.includes(item));
+
+  return filteredKeys;
+}
 function getDefaultValues(items) {
   const defaultValues = {
     value: {

+ 2 - 2
src/newtab/pages/workflows/[id].vue

@@ -5,9 +5,9 @@
       :class="
         editState.editing
           ? 'absolute h-full w-full md:relative z-50'
-          : 'hidden md:block'
+          : 'hidden md:flex'
       "
-      class="w-80 bg-white dark:bg-gray-800 py-6 border-l border-gray-100 dark:border-gray-700 dark:border-opacity-50 flex flex-col"
+      class="w-80 bg-white dark:bg-gray-800 py-6 border-l border-gray-100 dark:border-gray-700 dark:border-opacity-50 flex-col"
     >
       <workflow-edit-block
         v-if="editState.editing"

+ 28 - 21
src/newtab/utils/workflowEngine/blocksHandler/handlerConditions.js

@@ -3,7 +3,7 @@ import browser from 'webextension-polyfill';
 import compareBlockValue from '@/utils/compareBlockValue';
 import mustacheReplacer from '@/utils/referenceData/mustacheReplacer';
 import testConditions from '@/utils/testConditions';
-import { automaRefDataStr } from '../helper';
+import { automaRefDataStr, messageSandbox } from '../helper';
 
 const nanoid = customAlphabet('1234567890abcdef', 5);
 
@@ -45,11 +45,13 @@ function checkConditions(data, conditionOptions) {
     testAllConditions();
   });
 }
-function checkCodeCondition(activeTab, payload) {
+async function checkCodeCondition(activeTab, payload) {
   const variableId = nanoid();
 
-  return browser.scripting
-    .executeScript({
+  if (!payload.data.context || payload.data.context === 'website') {
+    if (!activeTab.id) throw new Error('no-tab');
+
+    const [{ result }] = await browser.scripting.executeScript({
       world: 'MAIN',
       args: [payload, variableId, automaRefDataStr(variableId)],
       target: {
@@ -62,22 +64,22 @@ function checkCodeCondition(activeTab, payload) {
 
           const scriptEl = document.createElement('script');
           scriptEl.textContent = `
-          (async () => {
-            const ${varName} = ${JSON.stringify(refData)};
-            ${refDataScript}
-            try {
-              ${data.code}
-            } catch (error) {
-              return {
-                $isError: true,
-                message: error.message,
+            (async () => {
+              const ${varName} = ${JSON.stringify(refData)};
+              ${refDataScript}
+              try {
+                ${data.code}
+              } catch (error) {
+                return {
+                  $isError: true,
+                  message: error.message,
+                }
               }
-            }
-          })()
-            .then((detail) => {
-              window.dispatchEvent(new CustomEvent('__automa-condition-code__', { detail }));
-            });
-        `;
+            })()
+              .then((detail) => {
+                window.dispatchEvent(new CustomEvent('__automa-condition-code__', { detail }));
+              });
+          `;
 
           document.documentElement.appendChild(scriptEl);
 
@@ -102,8 +104,13 @@ function checkCodeCondition(activeTab, payload) {
           );
         });
       },
-    })
-    .then(([result]) => result.result);
+    });
+    return result;
+  }
+  const result = await messageSandbox('conditionCode', payload);
+  if (result && result.$isError) throw new Error(result.message);
+
+  return result;
 }
 
 async function conditions({ data, id }, { prevBlockData, refData }) {

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

@@ -1,7 +1,7 @@
 import { customAlphabet } from 'nanoid/non-secure';
 import browser from 'webextension-polyfill';
 import cloneDeep from 'lodash.clonedeep';
-import { automaRefDataStr, waitTabLoaded } from '../helper';
+import { messageSandbox, automaRefDataStr, waitTabLoaded } from '../helper';
 
 const nanoid = customAlphabet('1234567890abcdef', 5);
 
@@ -26,23 +26,6 @@ function automaResetTimeout() {
 
   return str;
 }
-function executeInSandbox(data) {
-  return new Promise((resolve) => {
-    const messageId = nanoid();
-
-    const iframeEl = document.querySelector('#sandbox');
-    iframeEl.contentWindow.postMessage({ id: messageId, ...data }, '*');
-
-    const messageListener = ({ data: messageData }) => {
-      if (messageData?.type !== 'sandbox' || messageData?.id !== messageId)
-        return;
-
-      resolve(messageData.result);
-    };
-
-    window.addEventListener('message', messageListener, { once: true });
-  });
-}
 async function executeInWebpage(args, target) {
   const [{ result }] = await browser.scripting.executeScript({
     args,
@@ -232,7 +215,7 @@ export async function javascriptCode({ outputs, data, ...block }, { refData }) {
   }
 
   const result = await (data.context === 'background'
-    ? executeInSandbox({
+    ? messageSandbox('javascriptBlock', {
         preloadScripts,
         refData: payload.refData,
         blockData: cloneDeep(payload.data),

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

@@ -1,4 +1,25 @@
 import browser from 'webextension-polyfill';
+import { customAlphabet } from 'nanoid/non-secure';
+
+export function messageSandbox(type, data = {}) {
+  const nanoid = customAlphabet('1234567890abcdef', 5);
+
+  return new Promise((resolve) => {
+    const messageId = nanoid();
+
+    const iframeEl = document.querySelector('#sandbox');
+    iframeEl.contentWindow.postMessage({ id: messageId, type, ...data }, '*');
+
+    const messageListener = ({ data: messageData }) => {
+      if (messageData?.type !== 'sandbox' || messageData?.id !== messageId)
+        return;
+
+      resolve(messageData.result);
+    };
+
+    window.addEventListener('message', messageListener, { once: true });
+  });
+}
 
 export async function getFrames(tabId) {
   try {

+ 76 - 24
src/sandbox/index.js

@@ -2,9 +2,7 @@ import objectPath from 'object-path';
 
 window.$getNestedProperties = objectPath.get;
 
-function onMessage({ data }) {
-  if (!data.id) return;
-
+function handleJavascriptBlock(data) {
   let timeout;
   const scriptId = `script${data.id}`;
   const propertyName = `automa${data.id}`;
@@ -42,27 +40,27 @@ function onMessage({ data }) {
   const script = document.createElement('script');
   script.id = scriptId;
   script.textContent = `
-  	(() => {
-  		function automaRefData(keyword, path = '') {
-			  return window.$getNestedProperties(${propertyName}.refData, keyword + '.' + path);
-			}
-  		function automaSetVariable(name, value) {
-			  ${propertyName}.refData.variables[name] = value;
-			}
-  		function automaNextBlock(data = {}, insert = true) {
-  			${propertyName}.nextBlock({ data, insert });
-  		}
-  		function automaResetTimeout() {
-  			${propertyName}.resetTimeout();
-  		}
-
-  		try {
-  			${data.blockData.code}
-  		} catch (error) {
-  			console.error(error);
-  			automaNextBlock({ $error: true, message: error.message });
-  		}
-  	})();
+    (() => {
+      function automaRefData(keyword, path = '') {
+        return window.$getNestedProperties(${propertyName}.refData, keyword + '.' + path);
+      }
+      function automaSetVariable(name, value) {
+        ${propertyName}.refData.variables[name] = value;
+      }
+      function automaNextBlock(data = {}, insert = true) {
+        ${propertyName}.nextBlock({ data, insert });
+      }
+      function automaResetTimeout() {
+        ${propertyName}.resetTimeout();
+      }
+
+      try {
+        ${data.blockData.code}
+      } catch (error) {
+        console.error(error);
+        automaNextBlock({ $error: true, message: error.message });
+      }
+    })();
   `;
 
   function cleanUp() {
@@ -102,5 +100,59 @@ function onMessage({ data }) {
   timeout = setTimeout(cleanUp, data.blockData.timeout);
   document.body.appendChild(script);
 }
+function handleConditionCode(data) {
+  const propertyName = `automa${data.id}`;
+
+  const script = document.createElement('script');
+  script.textContent = `
+    (async () => {
+      function automaRefData(keyword, path = '') {
+        return window.$getNestedProperties(${propertyName}.refData, keyword + '.' + path);
+      }
+
+      try {
+        ${data.data.code}
+      } catch (error) {
+        return {
+          $isError: true,
+          message: error.message,
+        }
+      }
+    })()
+      .then((result) => {
+        ${propertyName}.done(result);
+      });
+  `;
+
+  window[propertyName] = {
+    refData: data.refData,
+    done: (result) => {
+      script.remove();
+      delete window[propertyName];
+
+      window.top.postMessage(
+        {
+          result,
+          id: data.id,
+          type: 'sandbox',
+        },
+        '*'
+      );
+    },
+  };
+
+  document.body.appendChild(script);
+}
+
+const eventHandlers = {
+  conditionCode: handleConditionCode,
+  javascriptBlock: handleJavascriptBlock,
+};
+
+function onMessage({ data }) {
+  if (!data.id || !data.type || !eventHandlers[data.type]) return;
+
+  eventHandlers[data.type](data);
+}
 
 window.addEventListener('message', onMessage);

+ 1 - 1
src/utils/shared.js

@@ -1542,7 +1542,7 @@ export const conditionBuilder = {
       category: 'value',
       name: 'Code',
       compareable: false,
-      data: { code: '\nreturn true;' },
+      data: { code: '\nreturn true;', context: 'background' },
     },
     {
       id: 'data#exists',