Ahmad Kholid %!s(int64=2) %!d(string=hai) anos
pai
achega
44e48c0d01
Modificáronse 100 ficheiros con 867 adicións e 643 borrados
  1. 1 1
      package.json
  2. 4 0
      src/assets/css/tailwind.css
  3. 9 4
      src/background/BackgroundUtils.js
  4. 22 5
      src/background/BackgroundWorkflowTriggers.js
  5. 4 11
      src/background/BackgroundWorkflowUtils.js
  6. 69 2
      src/background/index.js
  7. 34 6
      src/components/block/BlockBasic.vue
  8. 16 2
      src/components/block/BlockGroup.vue
  9. 2 2
      src/components/newtab/app/AppSidebar.vue
  10. 12 6
      src/components/newtab/shared/SharedConditionBuilder/ConditionBuilderInputs.vue
  11. 2 2
      src/components/newtab/shared/SharedLogsTable.vue
  12. 2 2
      src/components/newtab/shared/SharedWorkflowState.vue
  13. 36 3
      src/components/newtab/workflow/WorkflowEditor.vue
  14. 2 2
      src/components/newtab/workflow/WorkflowRunning.vue
  15. 5 1
      src/components/newtab/workflow/edit/EditBlockSettings.vue
  16. 1 1
      src/components/newtab/workflow/edit/EditGetText.vue
  17. 8 2
      src/components/newtab/workflow/edit/EditJavascriptCode.vue
  18. 1 1
      src/components/newtab/workflow/edit/InsertWorkflowData.vue
  19. 1 1
      src/components/newtab/workflow/editor/EditorLocalActions.vue
  20. 26 0
      src/components/newtab/workflow/settings/SettingsGeneral.vue
  21. 1 1
      src/components/newtab/workflows/WorkflowsHosted.vue
  22. 1 1
      src/components/newtab/workflows/WorkflowsLocal.vue
  23. 1 1
      src/components/newtab/workflows/WorkflowsShared.vue
  24. 1 1
      src/components/newtab/workflows/WorkflowsUserTeam.vue
  25. 3 1
      src/components/popup/home/HomeTeamWorkflows.vue
  26. 1 1
      src/content/blocksHandler/handlerConditions.js
  27. 11 1
      src/content/blocksHandler/handlerCreateElement.js
  28. 1 1
      src/content/blocksHandler/handlerJavascriptCode.js
  29. 4 1
      src/content/commandPalette/App.vue
  30. 1 1
      src/content/index.js
  31. 8 2
      src/content/services/recordWorkflow/App.vue
  32. 3 0
      src/locales/en/blocks.json
  33. 7 7
      src/locales/fr/newtab.json
  34. 4 1
      src/manifest.chrome.json
  35. 4 1
      src/manifest.firefox.json
  36. 31 4
      src/newtab/App.vue
  37. 1 1
      src/newtab/pages/Logs.vue
  38. 3 0
      src/newtab/pages/Recording.vue
  39. 1 1
      src/newtab/pages/Welcome.vue
  40. 96 17
      src/newtab/pages/Workflows.vue
  41. 3 3
      src/newtab/pages/logs/Running.vue
  42. 2 2
      src/newtab/pages/logs/[id].vue
  43. 1 1
      src/newtab/pages/workflows/Host.vue
  44. 13 2
      src/newtab/pages/workflows/[id].vue
  45. 8 5
      src/newtab/utils/elementSelector.js
  46. 0 96
      src/newtab/workflowEngine/blocksHandler/handlerCreateElement.js
  47. 6 3
      src/popup/App.vue
  48. 57 101
      src/popup/pages/Home.vue
  49. 0 236
      src/popup/pages/Recording.vue
  50. 0 6
      src/popup/router.js
  51. 1 1
      src/sandbox/utils/handleBlockExpression.js
  52. 3 42
      src/stores/hostedWorkflow.js
  53. 1 1
      src/stores/main.js
  54. 8 1
      src/stores/workflow.js
  55. 1 1
      src/utils/shared.js
  56. 4 2
      src/utils/testConditions.js
  57. 17 3
      src/utils/workflowData.js
  58. 2 1
      src/workflowEngine/WorkflowEngine.js
  59. 0 0
      src/workflowEngine/WorkflowLogger.js
  60. 0 0
      src/workflowEngine/WorkflowState.js
  61. 4 1
      src/workflowEngine/WorkflowWorker.js
  62. 5 12
      src/workflowEngine/blocksHandler.js
  63. 34 4
      src/workflowEngine/blocksHandler/handlerActiveTab.js
  64. 0 0
      src/workflowEngine/blocksHandler/handlerBlockPackage.js
  65. 0 0
      src/workflowEngine/blocksHandler/handlerBlocksGroup.js
  66. 0 0
      src/workflowEngine/blocksHandler/handlerBrowserEvent.js
  67. 3 0
      src/workflowEngine/blocksHandler/handlerClipboard.js
  68. 0 0
      src/workflowEngine/blocksHandler/handlerCloseTab.js
  69. 57 11
      src/workflowEngine/blocksHandler/handlerConditions.js
  70. 0 0
      src/workflowEngine/blocksHandler/handlerCookie.js
  71. 130 0
      src/workflowEngine/blocksHandler/handlerCreateElement.js
  72. 0 0
      src/workflowEngine/blocksHandler/handlerDataMapping.js
  73. 0 0
      src/workflowEngine/blocksHandler/handlerDelay.js
  74. 0 0
      src/workflowEngine/blocksHandler/handlerDeleteData.js
  75. 0 0
      src/workflowEngine/blocksHandler/handlerElementExists.js
  76. 0 0
      src/workflowEngine/blocksHandler/handlerExecuteWorkflow.js
  77. 0 0
      src/workflowEngine/blocksHandler/handlerExportData.js
  78. 0 0
      src/workflowEngine/blocksHandler/handlerForwardPage.js
  79. 0 0
      src/workflowEngine/blocksHandler/handlerGoBack.js
  80. 0 0
      src/workflowEngine/blocksHandler/handlerGoogleSheets.js
  81. 0 0
      src/workflowEngine/blocksHandler/handlerHandleDialog.js
  82. 0 0
      src/workflowEngine/blocksHandler/handlerHandleDownload.js
  83. 0 0
      src/workflowEngine/blocksHandler/handlerHoverElement.js
  84. 0 0
      src/workflowEngine/blocksHandler/handlerIncreaseVariable.js
  85. 10 2
      src/workflowEngine/blocksHandler/handlerInsertData.js
  86. 0 0
      src/workflowEngine/blocksHandler/handlerInteractionBlock.js
  87. 57 8
      src/workflowEngine/blocksHandler/handlerJavascriptCode.js
  88. 0 0
      src/workflowEngine/blocksHandler/handlerLogData.js
  89. 0 0
      src/workflowEngine/blocksHandler/handlerLoopBreakpoint.js
  90. 0 0
      src/workflowEngine/blocksHandler/handlerLoopData.js
  91. 0 0
      src/workflowEngine/blocksHandler/handlerLoopElements.js
  92. 0 0
      src/workflowEngine/blocksHandler/handlerNewTab.js
  93. 0 0
      src/workflowEngine/blocksHandler/handlerNewWindow.js
  94. 0 0
      src/workflowEngine/blocksHandler/handlerNotification.js
  95. 0 0
      src/workflowEngine/blocksHandler/handlerParameterPrompt.js
  96. 0 0
      src/workflowEngine/blocksHandler/handlerProxy.js
  97. 0 0
      src/workflowEngine/blocksHandler/handlerRegexVariable.js
  98. 0 0
      src/workflowEngine/blocksHandler/handlerReloadTab.js
  99. 0 0
      src/workflowEngine/blocksHandler/handlerRepeatTask.js
  100. 0 0
      src/workflowEngine/blocksHandler/handlerSaveAssets.js

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "automa",
-  "version": "1.22.0",
+  "version": "1.23.0",
   "description": "An extension for automating your browser by connecting blocks",
   "repository": {
     "type": "git",

+ 4 - 0
src/assets/css/tailwind.css

@@ -32,6 +32,10 @@
   @apply dark:border-gray-700;
 }
 
+html.dark {
+  @apply bg-gray-900;
+}
+
 body, :host {
   font-family: 'Inter var' !important;
   font-size: 16px !important;

+ 9 - 4
src/background/BackgroundUtils.js

@@ -1,5 +1,5 @@
 import browser from 'webextension-polyfill';
-import { waitTabLoaded } from '@/newtab/workflowEngine/helper';
+import { waitTabLoaded } from '@/workflowEngine/helper';
 
 class BackgroundUtils {
   static async openDashboard(url, updateTab = true) {
@@ -27,12 +27,17 @@ class BackgroundUtils {
       } else {
         const windowOptions = {
           url: tabUrl,
-          height: 715,
-          width: 750,
           type: 'popup',
-          focused: updateTab,
         };
 
+        if (updateTab) {
+          windowOptions.height = 715;
+          windowOptions.width = 715;
+          windowOptions.focused = true;
+        } else {
+          windowOptions.state = 'minimized';
+        }
+
         await browser.windows.create(windowOptions);
       }
     } catch (error) {

+ 22 - 5
src/background/BackgroundWorkflowTriggers.js

@@ -6,8 +6,26 @@ import {
   registerSpecificDay,
   registerWorkflowTrigger,
 } from '@/utils/workflowTrigger';
+import BackgroundUtils from './BackgroundUtils';
 import BackgroundWorkflowUtils from './BackgroundWorkflowUtils';
 
+async function executeWorkflow(workflowData, options) {
+  if (workflowData.isDisabled) return;
+
+  const isMV2 = browser.runtime.getManifest().manifest_version === 2;
+  const context = workflowData.settings.execContext;
+  if (isMV2 || context === 'background') {
+    BackgroundWorkflowUtils.executeWorkflow(workflowData, options);
+    return;
+  }
+
+  await BackgroundUtils.openDashboard('', false);
+  await BackgroundUtils.sendMessageToDashboard('workflow:execute', {
+    data: workflowData,
+    options,
+  });
+}
+
 class BackgroundWorkflowTriggers {
   static async visitWebTriggers(tabId, tabUrl) {
     const { visitWebTriggers } = await browser.storage.local.get(
@@ -31,8 +49,7 @@ class BackgroundWorkflowTriggers {
       const workflowData = await BackgroundWorkflowUtils.getWorkflow(
         workflowId
       );
-      if (workflowData)
-        BackgroundWorkflowUtils.executeWorkflow(workflowData, { tabId });
+      if (workflowData) executeWorkflow(workflowData, { tabId });
     }
   }
 
@@ -99,7 +116,7 @@ class BackgroundWorkflowTriggers {
         if (isAfter) return;
       }
 
-      BackgroundWorkflowUtils.executeWorkflow(currentWorkflow);
+      executeWorkflow(currentWorkflow);
 
       if (!data) return;
 
@@ -135,7 +152,7 @@ class BackgroundWorkflowTriggers {
       const workflowData = await BackgroundWorkflowUtils.getWorkflow(
         workflowId
       );
-      BackgroundWorkflowUtils.executeWorkflow(workflowData, {
+      executeWorkflow(workflowData, {
         data: {
           variables: message,
         },
@@ -180,7 +197,7 @@ class BackgroundWorkflowTriggers {
 
       if (triggerBlock) {
         if (isStartup && triggerBlock.type === 'on-startup') {
-          BackgroundWorkflowUtils.executeWorkflow(currWorkflow);
+          executeWorkflow(currWorkflow);
         } else {
           if (isStartup && triggerBlock.triggers) {
             for (const trigger of triggerBlock.triggers) {

+ 4 - 11
src/background/BackgroundWorkflowUtils.js

@@ -1,5 +1,5 @@
 import browser from 'webextension-polyfill';
-import BackgroundUtils from './BackgroundUtils';
+import { startWorkflowExec } from '@/workflowEngine';
 
 class BackgroundWorkflowUtils {
   static flattenTeamWorkflows(workflows) {
@@ -40,16 +40,9 @@ class BackgroundWorkflowUtils {
   }
 
   static async executeWorkflow(workflowData, options) {
-    await BackgroundUtils.openDashboard('', false);
-    const result = await BackgroundUtils.sendMessageToDashboard(
-      'workflow:execute',
-      {
-        data: workflowData,
-        options,
-      }
-    );
-
-    return result;
+    if (workflowData.isDisabled) return;
+
+    startWorkflowExec(workflowData, options, false);
   }
 }
 

+ 69 - 2
src/background/index.js

@@ -3,6 +3,7 @@ import { MessageListener } from '@/utils/message';
 import { sleep } from '@/utils/helper';
 import getFile from '@/utils/getFile';
 import automa from '@business';
+import { workflowState } from '@/workflowEngine';
 import { registerWorkflowTrigger } from '../utils/workflowTrigger';
 import BackgroundUtils from './BackgroundUtils';
 import BackgroundWorkflowUtils from './BackgroundWorkflowUtils';
@@ -98,7 +99,7 @@ message.on('get:tab-screenshot', (options, sender) =>
 
 message.on('dashboard:refresh-packages', async () => {
   const tabs = await browser.tabs.query({
-    url: chrome.runtime.getURL('/newtab.html'),
+    url: browser.runtime.getURL('/newtab.html'),
   });
 
   tabs.forEach((tab) => {
@@ -108,7 +109,20 @@ message.on('dashboard:refresh-packages', async () => {
   });
 });
 
-message.on('workflow:execute', (workflowData, sender) => {
+message.on('workflow:stop', (stateId) => workflowState.stop(stateId));
+message.on('workflow:execute', async (workflowData, sender) => {
+  const context = workflowData.settings.execContext;
+  const isMV2 = browser.runtime.getManifest().manifest_version === 2;
+  if (!isMV2 && (!context || context === 'popup')) {
+    await BackgroundUtils.openDashboard('', false);
+    await sleep(1000);
+    await BackgroundUtils.sendMessageToDashboard('workflow:execute', {
+      data: workflowData,
+      options: workflowData.option,
+    });
+    return;
+  }
+
   if (workflowData.includeTabId) {
     if (!workflowData.options) workflowData.options = {};
 
@@ -156,7 +170,60 @@ message.on(
 message.on('workflow:register', ({ triggerBlock, workflowId }) => {
   registerWorkflowTrigger(workflowId, triggerBlock);
 });
+message.on('recording:stop', async () => {
+  try {
+    await BackgroundUtils.openDashboard('', false);
+    await BackgroundUtils.sendMessageToDashboard('recording:stop');
+  } catch (error) {
+    console.error(error);
+  }
+});
 
 automa('background', message);
 
 browser.runtime.onMessage.addListener(message.listener());
+
+/* eslint-disable no-use-before-define */
+
+const isMV2 = browser.runtime.getManifest().manifest_version === 2;
+let lifeline;
+async function keepAlive() {
+  if (lifeline) return;
+  for (const tab of await browser.tabs.query({ url: '*://*/*' })) {
+    try {
+      await browser.scripting.executeScript({
+        target: { tabId: tab.id },
+        func: () => chrome.runtime.connect({ name: 'keepAlive' }),
+      });
+      browser.tabs.onUpdated.removeListener(retryOnTabUpdate);
+      return;
+    } catch (e) {
+      // Do nothing
+    }
+  }
+  browser.tabs.onUpdated.addListener(retryOnTabUpdate);
+}
+async function retryOnTabUpdate(tabId, info) {
+  if (info.url && /^(file|https?):/.test(info.url)) {
+    keepAlive();
+  }
+}
+function keepAliveForced() {
+  lifeline?.disconnect();
+  lifeline = null;
+  keepAlive();
+}
+
+if (!isMV2) {
+  browser.runtime.onConnect.addListener((port) => {
+    if (port.name === 'keepAlive') {
+      lifeline = port;
+      /* eslint-disable-next-line */
+      console.log('Stayin alive: ', new Date());
+      setTimeout(keepAliveForced, 295e3);
+      port.onDisconnect.addListener(keepAliveForced);
+    }
+  });
+
+  keepAlive();
+}

+ 34 - 6
src/components/block/BlockBasic.vue

@@ -41,13 +41,13 @@
           {{ data.description }}
         </p>
         <span
-          v-if="loopBlocks.includes(block.details.id) && data.loopId"
+          v-if="showTextToCopy"
+          :title="showTextToCopy.name + ' (click to copy)'"
           class="bg-box-transparent rounded-br-lg text-gray-600 dark:text-gray-200 text-overflow rounded-sm py-px px-1 text-xs absolute bottom-0 right-0"
-          title="Loop Id (click to copy)"
           style="max-width: 40%; cursor: pointer"
-          @click.stop="copyLoopId"
+          @click.stop="insertToClipboard(showTextToCopy.value)"
         >
-          {{ data.loopId }}
+          {{ state.isCopied ? '✅ Copied' : showTextToCopy.value }}
         </span>
       </div>
     </div>
@@ -77,6 +77,7 @@
   </block-base>
 </template>
 <script setup>
+import { computed, shallowReactive } from 'vue';
 import { Handle, Position } from '@vue-flow/core';
 import { useI18n } from 'vue-i18n';
 import { useEditorBlock } from '@/composable/editorBlock';
@@ -117,8 +118,35 @@ const { t, te } = useI18n();
 const block = useEditorBlock(props.label);
 const componentId = useComponentId('block-base');
 
-function copyLoopId() {
-  navigator.clipboard.writeText(props.data.loopId);
+const state = shallowReactive({
+  isCopied: false,
+});
+
+const showTextToCopy = computed(() => {
+  if (loopBlocks.includes(block.details.id) && props.data.loopId) {
+    return {
+      name: 'Loop id',
+      value: props.data.loopId,
+    };
+  }
+
+  if (block.details.id === 'google-sheets' && props.data.refKey) {
+    return {
+      name: 'Reference key',
+      value: props.data.refKey,
+    };
+  }
+
+  return null;
+});
+
+function insertToClipboard(text) {
+  navigator.clipboard.writeText(text);
+
+  state.isCopied = true;
+  setTimeout(() => {
+    state.isCopied = false;
+  }, 1000);
 }
 function getBlockName() {
   const key = `workflow.blocks.${block.details.id}.name`;

+ 16 - 2
src/components/block/BlockGroup.vue

@@ -76,13 +76,19 @@
           <div class="invisible group-hover:visible">
             <v-remixicon
               name="riPencilLine"
-              size="20"
+              size="18"
               class="cursor-pointer inline-block mr-2"
               @click="editBlock(element)"
             />
+            <v-remixicon
+              name="riSettings3Line"
+              size="18"
+              class="cursor-pointer inline-block mr-2"
+              @click="editItemSettings(element)"
+            />
             <v-remixicon
               name="riDeleteBin7Line"
-              size="20"
+              size="18"
               class="cursor-pointer inline-block"
               @click="deleteItem(index, element.itemId)"
             />
@@ -149,6 +155,14 @@ const blocks = computed(() =>
     : Object.values(props.data.blocks)
 );
 
+function editItemSettings(element) {
+  emit('settings', {
+    blockId: props.id,
+    data: element.data,
+    itemId: element.itemId,
+    details: { id: element.id },
+  });
+}
 function onDragStart(item, event) {
   event.dataTransfer.setData(
     'block',

+ 2 - 2
src/components/newtab/app/AppSidebar.vue

@@ -179,7 +179,7 @@ const tabs = [
 ];
 const hoverIndicator = ref(null);
 const showHoverIndicator = ref(false);
-const runningWorkflowsLen = computed(() => workflowStore.states.length);
+const runningWorkflowsLen = computed(() => workflowStore.getAllStates.length);
 
 useShortcut(
   tabs.reduce((acc, { shortcut }) => {
@@ -208,7 +208,7 @@ async function injectElementSelector() {
       return;
     }
 
-    await initElementSelector(tab);
+    await initElementSelector();
   } catch (error) {
     console.error(error);
   }

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

@@ -34,15 +34,20 @@
             class="mr-2"
           >
             <option
-              v-for="context in ['website', 'background']"
-              :key="context"
-              :disabled="isFirefox && context === 'background'"
-              :value="context"
+              :disabled="
+                isFirefox ||
+                (workflow?.data?.value.settings?.execContext || 'popup') !==
+                  'popup'
+              "
+              value="background"
             >
               {{
-                t(`workflow.blocks.javascript-code.context.items.${context}`)
+                t(`workflow.blocks.javascript-code.context.items.background`)
               }}
             </option>
+            <option value="website">
+              {{ t(`workflow.blocks.javascript-code.context.items.website`) }}
+            </option>
           </ui-select>
           <v-remixicon
             :title="t('workflow.conditionBuilder.topAwait')"
@@ -94,7 +99,7 @@
   </div>
 </template>
 <script setup>
-import { ref, watch, defineAsyncComponent } from 'vue';
+import { ref, watch, defineAsyncComponent, inject } from 'vue';
 import { nanoid } from 'nanoid';
 import { useI18n } from 'vue-i18n';
 import { autocompletion } from '@codemirror/autocomplete';
@@ -143,6 +148,7 @@ const conditionOperators = conditionBuilder.compareTypes.reduce((acc, type) => {
 }, {});
 
 const excludeData = ['context'];
+const workflow = inject('workflow');
 
 const { t } = useI18n();
 const inputsData = ref(cloneDeep(props.data));

+ 2 - 2
src/components/newtab/shared/SharedLogsTable.vue

@@ -114,7 +114,7 @@
 import { reactive } from 'vue';
 import { useI18n } from 'vue-i18n';
 import { countDuration } from '@/utils/helper';
-import { workflowState } from '@/newtab/workflowEngine';
+import { stopWorkflowExec } from '@/workflowEngine';
 import dayjs from '@/lib/dayjs';
 
 defineProps({
@@ -144,7 +144,7 @@ function getTranslation(key, defText = '') {
   return te(key) ? t(key) : defText;
 }
 function stopWorkflow(stateId) {
-  workflowState.stop(stateId);
+  stopWorkflowExec(stateId);
 }
 function toggleSelectedLog(selected, id) {
   if (selected) {

+ 2 - 2
src/components/newtab/shared/SharedWorkflowState.vue

@@ -59,7 +59,7 @@
 import browser from 'webextension-polyfill';
 import { useI18n } from 'vue-i18n';
 import { getBlocks } from '@/utils/getSharedData';
-import { workflowState } from '@/newtab/workflowEngine';
+import { stopWorkflowExec } from '@/workflowEngine';
 import dayjs from '@/lib/dayjs';
 
 const props = defineProps({
@@ -81,6 +81,6 @@ function openTab() {
   browser.tabs.update(props.data.state.tabId, { active: true });
 }
 function stopWorkflow() {
-  workflowState.stop(props.data.id);
+  stopWorkflowExec(props.data.id);
 }
 </script>

+ 36 - 3
src/components/newtab/workflow/WorkflowEditor.vue

@@ -72,7 +72,7 @@
     >
       <edit-block-settings
         :data="blockSettingsState.data"
-        @change="updateBlockData(blockSettingsState.data.blockId, $event)"
+        @change="updateBlockSettingsData"
       />
     </ui-modal>
   </vue-flow>
@@ -124,7 +124,13 @@ const props = defineProps({
   },
   disabled: Boolean,
 });
-const emit = defineEmits(['edit', 'init', 'update:node', 'delete:node']);
+const emit = defineEmits([
+  'edit',
+  'init',
+  'update:node',
+  'delete:node',
+  'update:settings',
+]);
 
 const fallbackBlocks = {
   BlockBasic: ['BlockExportData'],
@@ -190,8 +196,9 @@ const blockSettingsState = reactive({
   data: {},
 });
 
-function initEditBlockSettings({ blockId, details, data }) {
+function initEditBlockSettings({ blockId, details, data, itemId }) {
   blockSettingsState.data = {
+    itemId,
     blockId,
     id: details.id,
     data: cloneDeep(data),
@@ -218,6 +225,32 @@ function updateBlockData(nodeId, data = {}) {
 
   emit('update:node', node);
 }
+function updateBlockSettingsData(newSettings) {
+  if (isDisabled.value) return;
+
+  const nodeId = blockSettingsState.data.blockId;
+  const node = editor.getNode.value(nodeId);
+
+  if (blockSettingsState.data.itemId) {
+    const index = node.data.blocks.findIndex(
+      (item) => item.itemId === blockSettingsState.data.itemId
+    );
+    if (index === -1) return;
+
+    node.data.blocks[index].data = {
+      ...node.data.blocks[index].data,
+      ...newSettings,
+    };
+  } else {
+    node.data = { ...node.data, ...newSettings };
+  }
+
+  emit('update:settings', {
+    settings: newSettings,
+    itemId: blockSettingsState.data.itemId,
+    blockId: blockSettingsState.data.blockId,
+  });
+}
 function editBlock({ id, label, data }, additionalData = {}) {
   if (isDisabled.value) return;
 

+ 2 - 2
src/components/newtab/workflow/WorkflowRunning.vue

@@ -43,7 +43,7 @@
 import browser from 'webextension-polyfill';
 import { useI18n } from 'vue-i18n';
 import { getBlocks } from '@/utils/getSharedData';
-import { workflowState } from '@/newtab/workflowEngine';
+import { stopWorkflowExec } from '@/workflowEngine';
 import dayjs from '@/lib/dayjs';
 
 defineProps({
@@ -70,6 +70,6 @@ function openTab(tabId) {
   browser.tabs.update(tabId, { active: true });
 }
 function stopWorkflow(item) {
-  workflowState.stop(item);
+  stopWorkflowExec(item);
 }
 </script>

+ 5 - 1
src/components/newtab/workflow/edit/EditBlockSettings.vue

@@ -62,11 +62,15 @@ const tabs = [
 const isDebugSupported =
   browserType !== 'firefox' && supportedBlocks.includes(props.data.id);
 
+if (props.data?.itemId) {
+  tabs.pop();
+}
+
 if (isDebugSupported) {
   currActiveTab = 'general';
   tabs.unshift({ id: 'general', name: t('settings.menu.general') });
 } else if (!isOnErrorSupported) {
-  currActiveTab = 'lines';
+  if (!props.data?.itemId) currActiveTab = 'lines';
   tabs.shift();
 }
 

+ 1 - 1
src/components/newtab/workflow/edit/EditGetText.vue

@@ -11,7 +11,7 @@
       />
       <ui-popover>
         <template #trigger>
-          <button>/{{ regexExp.join('') }}</button>
+          <button>/{{ regexExp.join('') || 'flags' }}</button>
         </template>
         <p class="mb-2 text-gray-600 dark:text-gray-200">Expression flags</p>
         <div class="space-y-1">

+ 8 - 2
src/components/newtab/workflow/edit/EditJavascriptCode.vue

@@ -17,7 +17,11 @@
         @change="updateData({ timeout: +$event })"
       />
       <ui-select
-        v-if="!isFirefox"
+        v-if="
+          !isFirefox &&
+          (!workflow?.data?.value.settings?.execContext ||
+            workflow?.data?.value.settings?.execContext === 'popup')
+        "
         :model-value="data.context"
         :label="t('workflow.blocks.javascript-code.context.name')"
         class="mb-2 w-full"
@@ -145,7 +149,7 @@
   </div>
 </template>
 <script setup>
-import { watch, reactive, defineAsyncComponent } from 'vue';
+import { watch, reactive, defineAsyncComponent, inject } from 'vue';
 import { useI18n } from 'vue-i18n';
 import { autocompletion } from '@codemirror/autocomplete';
 import {
@@ -193,6 +197,8 @@ const availableFuncs = [
 ];
 const autocompleteList = Object.values(automaFuncsSnippets).slice(0, 4);
 
+const workflow = inject('workflow');
+
 const state = reactive({
   activeTab: 'code',
   code: `${props.data.code}`,

+ 1 - 1
src/components/newtab/workflow/edit/InsertWorkflowData.vue

@@ -17,7 +17,7 @@
       @change="updateData({ variableName: $event })"
     />
   </template>
-  <template v-if="table && workflow.columns?.value">
+  <template v-if="table && !workflow?.isPackage && workflow.columns?.value">
     <ui-checkbox
       :model-value="data.saveData"
       block

+ 1 - 1
src/components/newtab/workflow/editor/EditorLocalActions.vue

@@ -327,7 +327,7 @@ import { tagColors } from '@/utils/shared';
 import { parseJSON, findTriggerBlock } from '@/utils/helper';
 import { exportWorkflow, convertWorkflow } from '@/utils/workflowData';
 import { registerWorkflowTrigger } from '@/utils/workflowTrigger';
-import { executeWorkflow } from '@/newtab/workflowEngine';
+import { executeWorkflow } from '@/workflowEngine';
 import getTriggerText from '@/utils/triggerText';
 import convertWorkflowData from '@/utils/convertWorkflowData';
 import WorkflowShareTeam from '@/components/newtab/workflow/WorkflowShareTeam.vue';

+ 26 - 0
src/components/newtab/workflow/settings/SettingsGeneral.vue

@@ -32,6 +32,29 @@
       </span>
     </div>
   </div>
+  <div v-if="!isMV2" class="flex items-center pt-4">
+    <div class="mr-4 flex-1">
+      <p>Workflow Execution</p>
+      <p class="text-gray-600 dark:text-gray-200 text-sm leading-tight">
+        Workflow execution environment (Use "Popup" if workflow runs more than 5
+        minutes)
+      </p>
+    </div>
+    <a
+      href="https://docs.automa.site/workflow/settings.html#workflow-execution"
+      class="mr-2"
+      target="_blank"
+    >
+      <v-remixicon name="riInformationLine" />
+    </a>
+    <ui-select
+      :model-value="settings.execContext || 'popup'"
+      @change="updateSetting('execContext', $event)"
+    >
+      <option value="popup">Popup</option>
+      <option value="background">Background</option>
+    </ui-select>
+  </div>
   <div class="flex items-center pt-4">
     <div class="mr-4 flex-1">
       <p>
@@ -125,6 +148,7 @@
 <script setup>
 import { useI18n } from 'vue-i18n';
 import { useToast } from 'vue-toastification';
+import browser from 'webextension-polyfill';
 import { clearCache } from '@/utils/helper';
 import { useHasPermissions } from '@/composable/hasPermissions';
 
@@ -140,6 +164,8 @@ const { t } = useI18n();
 const toast = useToast();
 const permissions = useHasPermissions(['notifications']);
 
+const isMV2 = browser.runtime.getManifest().manifest_version === 2;
+
 const browserType = BROWSER_TYPE;
 const onError = [
   {

+ 1 - 1
src/components/newtab/workflows/WorkflowsHosted.vue

@@ -15,7 +15,7 @@ import { useI18n } from 'vue-i18n';
 import { useDialog } from '@/composable/dialog';
 import { arraySorter } from '@/utils/helper';
 import { useHostedWorkflowStore } from '@/stores/hostedWorkflow';
-import { executeWorkflow } from '@/newtab/workflowEngine';
+import { executeWorkflow } from '@/workflowEngine';
 import SharedCard from '@/components/newtab/shared/SharedCard.vue';
 
 const props = defineProps({

+ 1 - 1
src/components/newtab/workflows/WorkflowsLocal.vue

@@ -112,7 +112,7 @@ import { useDialog } from '@/composable/dialog';
 import { useWorkflowStore } from '@/stores/workflow';
 import { exportWorkflow } from '@/utils/workflowData';
 import { useSharedWorkflowStore } from '@/stores/sharedWorkflow';
-import { executeWorkflow } from '@/newtab/workflowEngine';
+import { executeWorkflow } from '@/workflowEngine';
 import WorkflowsLocalCard from './WorkflowsLocalCard.vue';
 
 const props = defineProps({

+ 1 - 1
src/components/newtab/workflows/WorkflowsShared.vue

@@ -12,7 +12,7 @@
 import { computed } from 'vue';
 import { useSharedWorkflowStore } from '@/stores/sharedWorkflow';
 import { arraySorter } from '@/utils/helper';
-import { executeWorkflow } from '@/newtab/workflowEngine';
+import { executeWorkflow } from '@/workflowEngine';
 import SharedCard from '@/components/newtab/shared/SharedCard.vue';
 
 const props = defineProps({

+ 1 - 1
src/components/newtab/workflows/WorkflowsUserTeam.vue

@@ -61,7 +61,7 @@ import { useTeamWorkflowStore } from '@/stores/teamWorkflow';
 import { arraySorter } from '@/utils/helper';
 import { useDialog } from '@/composable/dialog';
 import { tagColors } from '@/utils/shared';
-import { executeWorkflow } from '@/newtab/workflowEngine';
+import { executeWorkflow } from '@/workflowEngine';
 import SharedCard from '@/components/newtab/shared/SharedCard.vue';
 
 const props = defineProps({

+ 3 - 1
src/components/popup/home/HomeTeamWorkflows.vue

@@ -35,7 +35,6 @@ import { useUserStore } from '@/stores/user';
 import { sendMessage } from '@/utils/message';
 import { useTeamWorkflowStore } from '@/stores/teamWorkflow';
 import { tagColors } from '@/utils/shared';
-import { executeWorkflow } from '@/newtab/workflowEngine';
 import dayjs from '@/lib/dayjs';
 
 const props = defineProps({
@@ -60,6 +59,9 @@ function openWorkflowPage({ teamId, id }) {
   const url = `/teams/${teamId}/workflows/${id}`;
   sendMessage('open:dashboard', url, 'background');
 }
+function executeWorkflow(workflow) {
+  sendMessage('workflow:execute', workflow, 'background');
+}
 
 onMounted(() => {
   if (!userStore.user?.teams) return;

+ 1 - 1
src/content/blocksHandler/handlerConditions.js

@@ -1,6 +1,6 @@
 import { customAlphabet } from 'nanoid/non-secure';
 import { visibleInViewport, isXPath } from '@/utils/helper';
-import { automaRefDataStr } from '@/newtab/workflowEngine/helper';
+import { automaRefDataStr } from '@/workflowEngine/helper';
 import handleSelector from '../handleSelector';
 
 const nanoid = customAlphabet('1234567890abcdef', 5);

+ 11 - 1
src/content/blocksHandler/handlerCreateElement.js

@@ -37,7 +37,17 @@ async function createElement(block) {
     document.body.appendChild(style);
   }
 
-  if (data.injectJS) {
+  if (block.preloadCSS) {
+    block.preloadCSS.forEach((style) => {
+      const script = document.createElement('style');
+      script.id = `${baseId}-script`;
+      script.textContent = style.script;
+
+      document.body.appendChild(script);
+    });
+  }
+
+  if (!data?.dontInjectJS) {
     data.preloadScripts.forEach((item) => {
       const script = document.createElement(item.type);
       script.id = `${baseId}-script`;

+ 1 - 1
src/content/blocksHandler/handlerJavascriptCode.js

@@ -1,4 +1,4 @@
-import { jsContentHandler } from '@/newtab/utils/javascriptBlockUtil';
+import { jsContentHandler } from '@/workflowEngine/utils/javascriptBlockUtil';
 
 function javascriptCode({ data, isPreloadScripts, frameSelector }) {
   if (!isPreloadScripts) return jsContentHandler(...data);

+ 4 - 1
src/content/commandPalette/App.vue

@@ -184,6 +184,7 @@ import {
   inject,
 } from 'vue';
 import browser from 'webextension-polyfill';
+import cloneDeep from 'lodash.clonedeep';
 import workflowParameters from '@business/parameters';
 import { sendMessage } from '@/utils/message';
 import { debounce, parseJSON } from '@/utils/helper';
@@ -290,7 +291,7 @@ function executeWorkflow(workflow) {
     });
 
     paramsState.workflow = workflow;
-    paramsState.items = triggerData.parameters;
+    paramsState.items = cloneDeep(triggerData.parameters);
 
     paramsState.active = true;
   } else {
@@ -323,6 +324,8 @@ function getParamsValues(params) {
 function executeWorkflowWithParams() {
   const variables = getParamsValues(paramsState.items);
   sendExecuteCommand(paramsState.workflow, { data: { variables } });
+
+  clearParamsState();
 }
 function onKeydown(event) {
   const { ctrlKey, altKey, metaKey, key, shiftKey } = event;

+ 1 - 1
src/content/index.js

@@ -283,7 +283,7 @@ window.addEventListener('__automa-fetch__', (event) => {
   const { id, resource, type } = event.detail;
   const sendResponse = (payload) => {
     window.dispatchEvent(
-      new CustomEvent(`__autom-fetch-response-${id}__`, {
+      new CustomEvent(`__automa-fetch-response-${id}__`, {
         detail: { id, ...payload },
       })
     );

+ 8 - 2
src/content/services/recordWorkflow/App.vue

@@ -14,9 +14,10 @@
       @mousedown="toggleDragging(true, $event)"
     >
       <span
-        class="relative rounded-full bg-red-400 flex items-center justify-center"
-        title="Recording workflow"
+        class="relative cursor-pointer rounded-full bg-red-400 flex items-center justify-center"
         style="height: 24px; width: 24px"
+        title="Stop recording"
+        @click="stopRecording"
       >
         <v-remixicon
           name="riRecordCircleLine"
@@ -245,6 +246,11 @@ const blocksList = {
   default: ['get-text', 'attribute-value'],
 };
 
+function stopRecording() {
+  browser.runtime.sendMessage({
+    type: 'background--recording:stop',
+  });
+}
 function getElementBlocks(element) {
   if (!element) return;
 

+ 3 - 0
src/locales/en/blocks.json

@@ -483,6 +483,9 @@
           "placeholder": "(millisecond)"
         }
       },
+      "parameter-prompt": {
+        "name": "Parameter Prompt"
+      },
       "get-text": {
         "name": "Get text",
         "description": "Get text from an element",

+ 7 - 7
src/locales/fr/newtab.json

@@ -44,8 +44,8 @@
         "encrypt": "Chiffrer avec mot de passe"
       },
       "restore": {
-        "title": "Restorer les workflows",
-        "button": "Restorer",
+        "title": "Restaurer les workflows",
+        "button": "Restaurer",
         "update": "Mettre à jour si le workflow existe"
       },
       "cloud": {
@@ -54,7 +54,7 @@
           "cloud": "Cloud"
         },
         "delete": "Supprimer la sauvegarde",
-        "title": "Sauvegarde sur le cloud",
+        "title": "Enregistrer sur le cloud",
         "sync": "Synchroniser",
         "lastSync": "Dernière synchronisation",
         "lastBackup": "Dernière sauvegarde",
@@ -137,7 +137,7 @@
       "executeBy": "Exécuté par: \"{name}\""
     },
     "dataColumns": {
-      "title": "Table",
+      "title": "Tableau",
       "placeholder": "Rechercher ou ajouter une colonne",
       "select": "Sélectionner une colonne",
       "column": {
@@ -159,10 +159,10 @@
       "executedBlockOnWeb": "Afficher le bloc exécuté sur la page Web",
       "debugMode": {
         "title": "Mode debug",
-        "description": "Execute the workflow using the Chrome DevTools Protocol"
+        "description": "Exécutez le workflow à l'aide du protocole Chrome DevTools."
       },
       "restartWorkflow": {
-        "for": "Redémarrez pour",
+        "for": "Redémarrer pour",
         "times": "Fois"
       },
       "onError": {
@@ -232,7 +232,7 @@
       "stop-timeout": "Le workflow est arrêté en raison du délai d'attente",
       "no-file-access": "Automa n'a pas accès au fichier",
       "no-workflow": "Impossible de trouver le workflow avec l'ID \"{workflowId}\"",
-      "no-match-tab": "Impossible de trouver un onglet avec les patternes \"{pattern}\"",
+      "no-match-tab": "Impossible de trouver un onglet avec les motifs \"{pattern}\"",
       "no-clipboard-acces": "Vous n'êtes pas autorisé à accéder au presse-papiers",
       "element-not-found": "Impossible de trouver un élément avec le sélecteur \"{selector}\".",
       "not-iframe": "L'élément avec le sélecteur \"{selector}\" n'est pas un élément Iframe",

+ 4 - 1
src/manifest.chrome.json

@@ -1,7 +1,10 @@
 {
   "manifest_version": 3,
   "name": "Automa",
-  "action": {},
+  "action": {
+    "default_popup": "popup.html",
+    "default_icon": "icon-128.png"
+  },
   "background": {
     "service_worker": "background.bundle.js",
     "type": "module"

+ 4 - 1
src/manifest.firefox.json

@@ -10,7 +10,10 @@
     "scripts": ["background.bundle.js"],
     "persistent": false
   },
-  "browser_action": {},
+  "browser_action": {
+    "default_popup": "popup.html",
+    "default_icon": "icon-128.png"
+  },
   "icons": {
     "128": "icon-128.png"
   },

+ 31 - 4
src/newtab/App.vue

@@ -75,6 +75,7 @@ import { loadLocaleMessages, setI18nLanguage } from '@/lib/vueI18n';
 import { getUserWorkflows } from '@/utils/api';
 import { getWorkflowPermissions } from '@/utils/workflowData';
 import { sendMessage } from '@/utils/message';
+import { workflowState, startWorkflowExec } from '@/workflowEngine';
 import automa from '@business';
 import dbLogs from '@/db/logs';
 import dayjs from '@/lib/dayjs';
@@ -84,7 +85,6 @@ import dataMigration from '@/utils/dataMigration';
 import iconFirefox from '@/assets/svg/logoFirefox.svg';
 import iconChrome from '@/assets/svg/logo.svg';
 import SharedPermissionsModal from '@/components/newtab/shared/SharedPermissionsModal.vue';
-import { executeWorkflow } from './workflowEngine';
 
 let icon;
 if (window.location.protocol === 'moz-extension:') {
@@ -190,8 +190,15 @@ async function syncHostedWorkflows() {
     hostIds.push({ hostId, updatedAt: hostedWorkflows[hostId].updatedAt });
   });
 
+  if (hostIds.length === 0) return;
+
   await hostedWorkflowStore.fetchWorkflows(hostIds);
 }
+function stopRecording() {
+  if (!window.stopRecording) return;
+
+  window.stopRecording();
+}
 
 const messageEvents = {
   'refresh-packages': function () {
@@ -224,8 +231,10 @@ const messageEvents = {
     }
   },
   'workflow:execute': function ({ data, options = {} }) {
-    executeWorkflow(data, options);
+    startWorkflowExec(data, options);
   },
+  'recording:stop': stopRecording,
+  'background--recording:stop': stopRecording,
 };
 
 browser.runtime.onMessage.addListener(({ type, data }) => {
@@ -234,8 +243,14 @@ browser.runtime.onMessage.addListener(({ type, data }) => {
   messageEvents[type](data);
 });
 
+browser.storage.local.onChanged.addListener(({ workflowStates }) => {
+  if (!workflowStates) return;
+
+  workflowStore.states = Object.values(workflowStates.newValue);
+});
+
 useHead(() => {
-  const runningWorkflows = workflowStore.states.length;
+  const runningWorkflows = workflowStore.popupStates.length;
 
   return {
     title: 'Dashboard',
@@ -248,7 +263,7 @@ useHead(() => {
 
 /* eslint-disable-next-line */
 window.onbeforeunload = () => {
-  const runningWorkflows = workflowStore.states.length;
+  const runningWorkflows = workflowStore.popupStates.length;
   if (window.isDataChanged || runningWorkflows > 0) {
     return t('message.notSaved');
   }
@@ -279,6 +294,15 @@ window.addEventListener('message', ({ data }) => {
 
 (async () => {
   try {
+    workflowState.storage = {
+      get() {
+        return workflowStore.popupStates;
+      },
+      set(key, value) {
+        workflowStore.popupStates = Object.values(value);
+      },
+    };
+
     const tabs = await browser.tabs.query({
       url: browser.runtime.getURL('/newtab.html'),
     });
@@ -329,6 +353,9 @@ window.addEventListener('message', ({ data }) => {
     const { isRecording } = await browser.storage.local.get('isRecording');
     if (isRecording) {
       router.push('/recording');
+
+      await browser.action.setBadgeBackgroundColor({ color: '#ef4444' });
+      await browser.action.setBadgeText({ text: 'rec' });
     }
 
     autoDeleteLogs();

+ 1 - 1
src/newtab/pages/Logs.vue

@@ -11,7 +11,7 @@
     <div v-if="logs" style="min-height: 320px">
       <shared-logs-table
         :logs="logs"
-        :running="workflowStore.states"
+        :running="workflowStore.getAllStates"
         class="w-full"
       >
         <template #item-prepend="{ log }">

+ 3 - 0
src/newtab/pages/Recording.vue

@@ -252,6 +252,8 @@ onMounted(async () => {
 
   if (!isRecording && !recording) return;
 
+  window.stopRecording = stopRecording;
+
   browser.storage.onChanged.addListener(onStorageChanged);
   browser.tabs.onCreated.addListener(browserEvents.onTabCreated);
   browser.tabs.onActivated.addListener(browserEvents.onTabsActivated);
@@ -263,6 +265,7 @@ onMounted(async () => {
   Object.assign(state, recording);
 });
 onBeforeUnmount(() => {
+  window.stopRecording = null;
   browser.storage.local.onChanged.removeListener(onStorageChanged);
   browser.storage.onChanged.removeListener(onStorageChanged);
   browser.tabs.onCreated.removeListener(browserEvents.onTabCreated);

+ 1 - 1
src/newtab/pages/Welcome.vue

@@ -3,7 +3,7 @@
     <h1 class="font-semibold text-3xl mb-8">
       {{ t('welcome.title') }}
     </h1>
-    <p class="text-lg">
+    <p>
       Get started by reading the documentation or browsing workflows in the
       Automa Marketplace. <br />
       To learn how to use Automa, watch the tutorials on our YouTube Channel.

+ 96 - 17
src/newtab/pages/Workflows.vue

@@ -262,6 +262,44 @@
             />
           </ui-tab-panel>
         </ui-tab-panels>
+        <ui-card
+          v-if="workflowStore.isFirstTime"
+          class="mt-8 first-card relative dark:text-gray-200"
+        >
+          <v-remixicon
+            name="riCloseLine"
+            class="absolute top-4 right-4 cursor-pointer"
+            @click="workflowStore.isFirstTime = false"
+          />
+          <p>Create your first workflow by recording your actions:</p>
+          <ol class="list-decimal list-inside">
+            <li>Open your browser and go to your destination URL</li>
+            <li>
+              Click the "Record workflow" button, and do your simple repetitive
+              task
+            </li>
+            <li>
+              Need more help? Join
+              <a
+                href="https://discord.gg/C6khwwTE84"
+                target="_blank"
+                rel="noreferer"
+                >the community</a
+              >, or email us at
+              <a href="mailto:support@automa.site" target="_blank"
+                >support@automa.site</a
+              >
+            </li>
+          </ol>
+          <p class="mt-4">
+            Learn more about recording in
+            <a
+              href="https://docs.automa.site/guide/quick-start.html#recording-actions"
+              target="_blank"
+              >the documentation</a
+            >
+          </p>
+        </ui-card>
       </div>
     </div>
     <ui-modal v-model="addWorkflowModal.show" title="Workflow">
@@ -321,11 +359,13 @@ import { useToast } from 'vue-toastification';
 import { useDialog } from '@/composable/dialog';
 import { useShortcut } from '@/composable/shortcut';
 import { useGroupTooltip } from '@/composable/groupTooltip';
-import { isWhitespace } from '@/utils/helper';
+import { fetchApi } from '@/utils/api';
 import { useUserStore } from '@/stores/user';
 import { useWorkflowStore } from '@/stores/workflow';
 import { useTeamWorkflowStore } from '@/stores/teamWorkflow';
 import { useHostedWorkflowStore } from '@/stores/hostedWorkflow';
+import { registerWorkflowTrigger } from '@/utils/workflowTrigger';
+import { isWhitespace, findTriggerBlock } from '@/utils/helper';
 import { importWorkflow, getWorkflowPermissions } from '@/utils/workflowData';
 import recordWorkflow from '@/newtab/utils/startRecordWorkflow';
 import WorkflowsLocal from '@/components/newtab/workflows/WorkflowsLocal.vue';
@@ -408,6 +448,22 @@ function addWorkflow() {
   });
   clearAddWorkflowModal();
 }
+async function checkWorkflowPermissions(workflows) {
+  let requiredPermissions = [];
+
+  for (const workflow of workflows) {
+    if (workflow.drawflow) {
+      const permissions = await getWorkflowPermissions(workflow.drawflow);
+      requiredPermissions.push(...permissions);
+    }
+  }
+
+  requiredPermissions = Array.from(new Set(requiredPermissions));
+  if (requiredPermissions.length === 0) return;
+
+  permissionState.items = requiredPermissions;
+  permissionState.showModal = true;
+}
 function addHostedWorkflow() {
   dialog.prompt({
     async: true,
@@ -421,10 +477,41 @@ function addHostedWorkflow() {
       const hostId = value.replace(/\s/g, '');
 
       try {
-        await hostedWorkflowStore.addHostedWorkflow(hostId);
+        if (!userStore.user && hostedWorkflowStore.toArray.length >= 3)
+          throw new Error('rate-exceeded');
+
+        const isTheUserHost = userStore.getHostedWorkflows.some(
+          (host) => hostId === host.hostId
+        );
+        if (isTheUserHost) throw new Error('exist');
+
+        const response = await fetchApi('/workflows/hosted', {
+          method: 'POST',
+          body: JSON.stringify({ hostId }),
+        });
+        const result = await response.json();
+
+        if (!response.ok) {
+          const error = new Error(result.message);
+          error.data = result.data;
+
+          throw error;
+        }
+
+        if (result === null) throw new Error('not-found');
+
+        result.hostId = `${hostId}`;
+        result.createdAt = Date.now();
+
+        await checkWorkflowPermissions([result]);
+        await hostedWorkflowStore.insert(result, hostId);
+
+        const triggerBlock = findTriggerBlock(result.drawflow);
+        await registerWorkflowTrigger(hostId, triggerBlock);
 
         return true;
       } catch (error) {
+        console.error(error);
         const messages = {
           exists: t('workflow.host.messages.hostExist'),
           'rate-exceeded': t('message.rateExceeded'),
@@ -442,21 +529,7 @@ function addHostedWorkflow() {
 async function openImportDialog() {
   try {
     const workflows = await importWorkflow({ multiple: true });
-    const insertedWorkflows = Object.values(workflows);
-    let requiredPermissions = [];
-
-    for (const workflow of insertedWorkflows) {
-      if (workflow.drawflow) {
-        const permissions = await getWorkflowPermissions(workflow.drawflow);
-        requiredPermissions.push(...permissions);
-      }
-    }
-
-    requiredPermissions = Array.from(new Set(requiredPermissions));
-    if (requiredPermissions.length === 0) return;
-
-    permissionState.items = requiredPermissions;
-    permissionState.showModal = true;
+    await checkWorkflowPermissions(Object.values(workflows));
   } catch (error) {
     console.error(error);
   }
@@ -517,4 +590,10 @@ onMounted(() => {
 .workflows-container {
   @apply grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4;
 }
+
+.first-card {
+  a {
+    @apply text-blue-400 underline;
+  }
+}
 </style>

+ 3 - 3
src/newtab/pages/logs/Running.vue

@@ -80,7 +80,7 @@ import { useRoute, useRouter } from 'vue-router';
 import { useI18n } from 'vue-i18n';
 import { countDuration } from '@/utils/helper';
 import { useWorkflowStore } from '@/stores/workflow';
-import { workflowState } from '@/newtab/workflowEngine';
+import { stopWorkflowExec } from '@/workflowEngine';
 import dbLogs from '@/db/logs';
 import dayjs from '@/lib/dayjs';
 import LogsHistory from '@/components/newtab/logs/LogsHistory.vue';
@@ -96,11 +96,11 @@ const interval = setInterval(() => {
 }, 1000);
 
 const running = computed(() =>
-  workflowStore.states.find(({ id }) => id === route.params.id)
+  workflowStore.getAllStates.find(({ id }) => id === route.params.id)
 );
 
 function stopWorkflow() {
-  workflowState.stop(running.value.id);
+  stopWorkflowExec(running.value.id);
 }
 function getBlockPath(blockId) {
   const { workflowId, teamId } = running.value;

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

@@ -118,7 +118,7 @@ function deleteLog() {
     .equals(route.params.id)
     .delete()
     .then(() => {
-      if (backHistory.startsWith('/workflows')) {
+      if (backHistory?.startsWith('/workflows')) {
         router.replace(backHistory);
         return;
       }
@@ -129,7 +129,7 @@ function deleteLog() {
 function goToWorkflow() {
   let path = `/workflows/${currentLog.value.workflowId}`;
 
-  if (backHistory.startsWith(path)) {
+  if (backHistory?.startsWith(path)) {
     path = backHistory;
   }
 

+ 1 - 1
src/newtab/pages/workflows/Host.vue

@@ -120,7 +120,7 @@ import { useGroupTooltip } from '@/composable/groupTooltip';
 import { findTriggerBlock } from '@/utils/helper';
 import convertWorkflowData from '@/utils/convertWorkflowData';
 import { useWorkflowStore } from '@/stores/workflow';
-import { executeWorkflow } from '@/newtab/workflowEngine';
+import { executeWorkflow } from '@/workflowEngine';
 import { useHostedWorkflowStore } from '@/stores/hostedWorkflow';
 import getTriggerText from '@/utils/triggerText';
 import EditorLogs from '@/components/newtab/workflow/editor/EditorLogs.vue';

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

@@ -167,6 +167,7 @@
             @edit="initEditBlock"
             @update:node="state.dataChanged = true"
             @delete:node="state.dataChanged = true"
+            @update:settings="onUpdateBlockSettings"
           >
             <template
               v-if="!isTeamWorkflow || haveEditAccess"
@@ -320,9 +321,9 @@ import { excludeGroupBlocks } from '@/utils/shared';
 import { useGroupTooltip } from '@/composable/groupTooltip';
 import { useCommandManager } from '@/composable/commandManager';
 import { debounce, parseJSON, throttle } from '@/utils/helper';
-import { executeWorkflow } from '@/newtab/workflowEngine';
+import { executeWorkflow } from '@/workflowEngine';
 import { registerWorkflowTrigger } from '@/utils/workflowTrigger';
-import functions from '@/newtab/workflowEngine/templating/templatingFunctions';
+import functions from '@/workflowEngine/templating/templatingFunctions';
 import browser from 'webextension-polyfill';
 import dbStorage from '@/db/storage';
 import DroppedNode from '@/utils/editor/DroppedNode';
@@ -670,6 +671,15 @@ const onEdgesChange = debounce((changes) => {
   // if (command) commandManager.add(command);
 }, 250);
 
+function onUpdateBlockSettings({ blockId, itemId, settings }) {
+  state.dataChanged = true;
+
+  if (!editState.editing) return;
+  if (itemId && itemId !== editState.blockData.itemId) return;
+  if (editState.blockData.blockId !== blockId) return;
+
+  editState.blockData.data = { ...editState.blockData.data, ...settings };
+}
 function closeEditingCard() {
   editState.editing = false;
   editState.blockData = {};
@@ -1564,6 +1574,7 @@ provide('workflow-editor', editor);
 provide('autocompleteData', autocompleteList);
 provide('workflow', {
   editState,
+  isPackage,
   data: workflow,
   columns: workflowColumns,
 });

+ 8 - 5
src/newtab/utils/elementSelector.js

@@ -4,9 +4,16 @@ import { isXPath, sleep } from '@/utils/helper';
 const isMV2 = browser.runtime.getManifest().manifest_version === 2;
 
 async function getActiveTab() {
+  const currentWindow = await browser.windows.getCurrent();
+  if (currentWindow)
+    await browser.windows.update(currentWindow.id, { focused: false });
+
+  await sleep(200);
+
   const [tab] = await browser.tabs.query({
     active: true,
     url: '*://*/*',
+    lastFocusedWindow: true,
   });
   if (!tab) throw new Error('No active tab');
 
@@ -26,11 +33,7 @@ export async function initElementSelector(tab = null) {
   let activeTab = tab;
 
   if (!tab) {
-    const [queryTab] = await browser.tabs.query({
-      active: true,
-      url: '*://*/*',
-    });
-    activeTab = queryTab;
+    activeTab = await getActiveTab();
   }
 
   const result = await browser.tabs.sendMessage(activeTab.id, {

+ 0 - 96
src/newtab/workflowEngine/blocksHandler/handlerCreateElement.js

@@ -1,96 +0,0 @@
-import { customAlphabet } from 'nanoid/non-secure';
-import browser from 'webextension-polyfill';
-import { automaRefDataStr } from '../helper';
-
-const nanoid = customAlphabet('1234567890abcdef', 5);
-
-function getAutomaScript(refData) {
-  const varName = `automa${nanoid()}`;
-
-  const str = `
-const ${varName} = ${JSON.stringify(refData)};
-${automaRefDataStr(varName)}
-function automaSetVariable(name, value) {
-  ${varName}.variables[name] = value;
-}
-function automaExecWorkflow(options = {}) {
-  window.dispatchEvent(new CustomEvent('automa:execute-workflow', { detail: options }));
-}
-  `;
-
-  return str;
-}
-
-async function handleCreateElement(block, { refData }) {
-  if (!this.activeTab.id) throw new Error('no-tab');
-
-  const { data } = block;
-  const preloadScriptsPromise = await Promise.allSettled(
-    data.preloadScripts.map((item) => {
-      if (!item.src.startsWith('http'))
-        return Promise.reject(new Error('Invalid URL'));
-
-      return fetch(item.src)
-        .then((response) => response.text())
-        .then((result) => ({ type: item.type, script: result }));
-    })
-  );
-  const preloadScripts = preloadScriptsPromise.reduce((acc, item) => {
-    if (item.status === 'rejected') return acc;
-
-    acc.push(item.value);
-
-    return acc;
-  }, []);
-
-  data.preloadScripts = preloadScripts;
-
-  const payload = { ...block, data };
-
-  if (data.javascript && this.engine.isMV2) {
-    payload.data.injectJS = true;
-    payload.data.automaScript = getAutomaScript({ ...refData, secrets: {} });
-  }
-
-  await this._sendMessageToTab(payload, {}, data.runBeforeLoad ?? false);
-
-  if (data.javascript && !this.engine.isMV2) {
-    await browser.scripting.executeScript({
-      world: 'MAIN',
-      args: [
-        data.javascript,
-        block.id,
-        payload.script.automaScript,
-        preloadScripts,
-      ],
-      target: {
-        tabId: this.activeTab.id,
-        frameIds: [this.activeTab.frameId || 0],
-      },
-      func: (code, blockId, $automaScript, $preloadScripts) => {
-        const baseId = `automa-${blockId}`;
-
-        $preloadScripts.forEach((item) => {
-          const script = document.createElement(item.type);
-          script.id = `${baseId}-script`;
-          script.textContent = item.script;
-
-          document.body.appendChild(script);
-        });
-
-        const script = document.createElement('script');
-        script.id = `${baseId}-javascript`;
-        script.textContent = `(() => { ${$automaScript}\n${code} })()`;
-
-        document.body.appendChild(script);
-      },
-    });
-  }
-
-  return {
-    data: '',
-    nextBlockId: this.getBlockConnections(block.id),
-  };
-}
-
-export default handleCreateElement;

+ 6 - 3
src/popup/App.vue

@@ -6,22 +6,25 @@
 </template>
 <script setup>
 import { ref, onMounted } from 'vue';
-import { useRouter } from 'vue-router';
 import browser from 'webextension-polyfill';
 import { useStore } from '@/stores/main';
+import { sendMessage } from '@/utils/message';
 import { useWorkflowStore } from '@/stores/workflow';
 import { useHostedWorkflowStore } from '@/stores/hostedWorkflow';
 import { loadLocaleMessages, setI18nLanguage } from '@/lib/vueI18n';
 
 const store = useStore();
-const router = useRouter();
 const workflowStore = useWorkflowStore();
 const hostedWorkflowStore = useHostedWorkflowStore();
 
 const retrieved = ref(false);
 
 browser.storage.local.get('isRecording').then(({ isRecording }) => {
-  if (isRecording) router.push('/recording');
+  if (!isRecording) return;
+
+  sendMessage('open:dashboard', '/recording', 'background').then(() => {
+    window.close();
+  });
 });
 
 onMounted(async () => {

+ 57 - 101
src/popup/pages/Home.vue

@@ -10,14 +10,6 @@
     <div class="flex items-center mb-4">
       <h1 class="text-xl font-semibold text-white">Automa</h1>
       <div class="flex-grow"></div>
-      <ui-button
-        v-tooltip.group="t('home.record.title')"
-        icon
-        class="mr-2"
-        @click="state.newRecordingModal = true"
-      >
-        <v-remixicon name="riRecordCircleLine" />
-      </ui-button>
       <ui-button
         v-tooltip.group="
           t(`home.elementSelector.${state.haveAccess ? 'name' : 'noAccess'}`)
@@ -115,31 +107,29 @@
       @delete="deleteWorkflow"
       @toggle-pin="togglePinWorkflow(workflow)"
     />
-  </div>
-  <ui-modal v-model="state.newRecordingModal" custom-content>
-    <ui-card
-      :style="{ height: `${state.cardHeight}px` }"
-      class="w-full recording-card overflow-hidden rounded-b-none"
-      padding="p-0"
+    <div
+      v-if="state.showSettingsPopup"
+      class="bg-accent fixed bottom-5 left-0 m-4 p-4 rounded-lg dark:text-black text-white shadow-md"
     >
-      <div class="flex items-center px-4 pt-4 pb-2">
-        <p class="flex-1 font-semibold">
-          {{ t('home.record.title') }}
-        </p>
-        <v-remixicon
-          class="text-gray-600 dark:text-gray-300 cursor-pointer"
-          name="riCloseLine"
-          size="20"
-          @click="state.newRecordingModal = false"
-        ></v-remixicon>
-      </div>
-      <home-start-recording
-        @record="recordWorkflow"
-        @close="state.newRecordingModal = false"
-        @update="state.cardHeight = recordingCardHeight[$event] || 255"
+      <p class="leading-tight text-sm">
+        If the workflow runs for less than 5 minutes, you set it to run in the
+        background in
+        <a
+          href="https://docs.automa.site/workflow/settings.html#workflow-execution"
+          class="font-semibold underline"
+          target="_blank"
+        >
+          workflow settings.
+        </a>
+      </p>
+      <v-remixicon
+        name="riCloseLine"
+        class="absolute dark:text-gray-600 text-gray-300 top-2 right-2 cursor-pointer"
+        size="20"
+        @click="closeSettingsPopup"
       />
-    </ui-card>
-  </ui-modal>
+    </div>
+  </div>
 </template>
 <script setup>
 import { computed, onMounted, shallowReactive } from 'vue';
@@ -152,15 +142,13 @@ import { useWorkflowStore } from '@/stores/workflow';
 import { useGroupTooltip } from '@/composable/groupTooltip';
 import { useTeamWorkflowStore } from '@/stores/teamWorkflow';
 import { useHostedWorkflowStore } from '@/stores/hostedWorkflow';
+import { parseJSON } from '@/utils/helper';
+import { initElementSelector as initElementSelectorFunc } from '@/newtab/utils/elementSelector';
 import automa from '@business';
 import HomeWorkflowCard from '@/components/popup/home/HomeWorkflowCard.vue';
 import HomeTeamWorkflows from '@/components/popup/home/HomeTeamWorkflows.vue';
-import HomeStartRecording from '@/components/popup/home/HomeStartRecording.vue';
 
-const recordingCardHeight = {
-  new: 255,
-  existing: 480,
-};
+const isMV2 = browser.runtime.getManifest().manifest_version === 2;
 
 const { t } = useI18n();
 const dialog = useDialog();
@@ -179,7 +167,9 @@ const state = shallowReactive({
   haveAccess: true,
   activeTab: 'local',
   pinnedWorkflows: [],
-  newRecordingModal: false,
+  showSettingsPopup: isMV2
+    ? false
+    : parseJSON(localStorage.getItem('settingsPopup'), true) ?? true,
 });
 
 const pinnedWorkflows = computed(() => {
@@ -224,6 +214,10 @@ const showTab = computed(
   () => hostedWorkflowStore.toArray.length > 0 || userStore.user?.teams
 );
 
+function closeSettingsPopup() {
+  state.showSettingsPopup = false;
+  localStorage.setItem('settingsPopup', false);
+}
 function togglePinWorkflow(workflow) {
   const index = state.pinnedWorkflows.indexOf(workflow.id);
   const copyData = [...state.pinnedWorkflows];
@@ -239,8 +233,27 @@ function togglePinWorkflow(workflow) {
     pinnedWorkflows: copyData,
   });
 }
-function executeWorkflow(workflow) {
-  sendMessage('workflow:execute', workflow, 'background');
+async function executeWorkflow(workflow) {
+  try {
+    const [tab] = await browser.tabs.query({
+      url: browser.runtime.getURL('/newtab.html'),
+    });
+    if (tab && !isMV2) {
+      await browser.tabs.sendMessage(tab.id, {
+        type: 'workflow:execute',
+        data: {
+          data: workflow,
+          options: workflow?.options,
+        },
+      });
+    } else {
+      await sendMessage('workflow:execute', workflow, 'background');
+    }
+
+    window.close();
+  } catch (error) {
+    console.error(error);
+  }
 }
 function updateWorkflow(id, data) {
   return workflowStore.update({
@@ -274,71 +287,14 @@ function deleteWorkflow({ id, name }) {
   });
 }
 function openDashboard(url) {
-  sendMessage('open:dashboard', url, 'background');
-}
-async function initElementSelector() {
-  const [tab] = await browser.tabs.query({ active: true, currentWindow: true });
-  const result = await browser.tabs.sendMessage(tab.id, {
-    type: 'automa-element-selector',
+  sendMessage('open:dashboard', url, 'background').then(() => {
+    window.close();
   });
-
-  if (!result) {
-    await browser.tabs.executeScript(tab.id, {
-      allFrames: true,
-      file: './elementSelector.bundle.js',
-    });
-  }
-
-  window.close();
 }
-async function recordWorkflow(options = {}) {
-  try {
-    const flows = [];
-    const [activeTab] = await browser.tabs.query({
-      active: true,
-      currentWindow: true,
-    });
-
-    if (activeTab && activeTab.url.startsWith('http')) {
-      flows.push({
-        id: 'new-tab',
-        description: activeTab.url,
-        data: { url: activeTab.url },
-      });
-    }
-
-    await browser.storage.local.set({
-      isRecording: true,
-      recording: {
-        flows,
-        name: 'unnamed',
-        activeTab: {
-          id: activeTab.id,
-          url: activeTab.url,
-        },
-        ...options,
-      },
-    });
-    await browser.browserAction.setBadgeBackgroundColor({ color: '#ef4444' });
-    await browser.browserAction.setBadgeText({ text: 'rec' });
-
-    const tabs = await browser.tabs.query({});
-    for (const tab of tabs) {
-      if (
-        tab.url.startsWith('http') &&
-        !tab.url.includes('chrome.google.com')
-      ) {
-        await browser.tabs.executeScript(tab.id, {
-          allFrames: true,
-          file: 'recordWorkflow.bundle.js',
-        });
-      }
-    }
-
+function initElementSelector() {
+  initElementSelectorFunc().then(() => {
     window.close();
-  } catch (error) {
-    console.error(error);
-  }
+  });
 }
 function openWorkflowPage({ id, hostId }) {
   let url = `/workflows/${id}`;

+ 0 - 236
src/popup/pages/Recording.vue

@@ -1,236 +0,0 @@
-<template>
-  <div class="p-5">
-    <div class="flex items-center">
-      <button
-        v-tooltip="t('recording.stop')"
-        class="h-12 w-12 rounded-full focus:ring-0 bg-red-400 relative flex items-center justify-center"
-        @click="stopRecording"
-      >
-        <span
-          class="absolute animate-ping bg-red-400 rounded-full"
-          style="height: 80%; width: 80%; animation-duration: 1.3s"
-        ></span>
-        <ui-spinner v-if="state.isGenerating" color="text-white" />
-        <v-remixicon v-else name="riStopLine" class="z-10 relative" />
-      </button>
-      <div class="ml-4 flex-1 overflow-hidden">
-        <p class="text-sm">{{ t('recording.title') }}</p>
-        <p class="font-semibold text-xl leading-tight text-overflow">
-          {{ state.name }}
-        </p>
-      </div>
-    </div>
-    <p class="font-semibold mt-6 mb-2">Flows</p>
-    <ui-list class="space-y-1">
-      <ui-list-item
-        v-for="(item, index) in state.flows"
-        :key="index"
-        class="group"
-        small
-      >
-        <v-remixicon :name="tasks[item.id].icon" />
-        <div class="overflow-hidden flex-1 mx-2">
-          <p class="leading-tight">
-            {{ t(`workflow.blocks.${item.id}.name`) }}
-          </p>
-          <p
-            :title="item.data.description || item.description"
-            class="text-overflow text-sm leading-tight text-gray-600"
-          >
-            {{ item.data.description || item.description }}
-          </p>
-        </div>
-        <v-remixicon
-          name="riDeleteBin7Line"
-          class="invisible group-hover:visible cursor-pointer"
-          @click="removeBlock(index)"
-        />
-      </ui-list-item>
-    </ui-list>
-  </div>
-</template>
-<script setup>
-import { onMounted, reactive, toRaw } from 'vue';
-import { useI18n } from 'vue-i18n';
-import { useRouter } from 'vue-router';
-import { nanoid } from 'nanoid';
-import defu from 'defu';
-import browser from 'webextension-polyfill';
-import { tasks } from '@/utils/shared';
-import { useWorkflowStore } from '@/stores/workflow';
-
-const { t } = useI18n();
-const router = useRouter();
-const workflowStore = useWorkflowStore();
-
-const state = reactive({
-  name: '',
-  flows: [],
-  activeTab: {},
-  isGenerating: false,
-});
-
-function generateDrawflow(startBlock, startBlockData) {
-  let nextNodeId = nanoid();
-  const triggerId = startBlock?.id || nanoid();
-  let prevNodeId = startBlock?.id || triggerId;
-
-  const nodes = [];
-  const edges = [];
-
-  const addEdge = (data = {}) => {
-    edges.push({
-      ...data,
-      id: nanoid(),
-      class: `source-${data.sourceHandle} targte-${data.targetHandle}`,
-    });
-  };
-  addEdge({
-    source: prevNodeId,
-    target: nextNodeId,
-    targetHandle: `${nextNodeId}-input-1`,
-    sourceHandle: startBlock?.output || `${prevNodeId}-output-1`,
-  });
-
-  if (!startBlock) {
-    nodes.push({
-      position: {
-        x: 50,
-        y: 300,
-      },
-      id: triggerId,
-      label: 'trigger',
-      type: 'BlockBasic',
-      data: tasks.trigger.data,
-    });
-  }
-
-  const position = {
-    y: startBlockData ? startBlockData.position.y + 120 : 300,
-    x: startBlockData ? startBlockData.position.x + 280 : 320,
-  };
-  const groups = {};
-
-  state.flows.forEach((block, index) => {
-    if (block.groupId) {
-      if (!groups[block.groupId]) groups[block.groupId] = [];
-
-      groups[block.groupId].push({
-        id: block.id,
-        itemId: nanoid(),
-        data: defu(block.data, tasks[block.id].data),
-      });
-
-      const nextNodeInGroup = state.flows[index + 1]?.groupId;
-      if (nextNodeInGroup) return;
-
-      block.id = 'blocks-group';
-      block.data = { blocks: groups[block.groupId] };
-
-      delete groups[block.groupId];
-    }
-
-    const node = {
-      id: nextNodeId,
-      label: block.id,
-      type: tasks[block.id].component,
-      data: defu(block.data, tasks[block.id].data),
-      position: JSON.parse(JSON.stringify(position)),
-    };
-
-    prevNodeId = nextNodeId;
-    nextNodeId = nanoid();
-
-    if (index !== state.flows.length - 1) {
-      addEdge({
-        target: nextNodeId,
-        source: prevNodeId,
-        targetHandle: `${nextNodeId}-input-1`,
-        sourceHandle: `${prevNodeId}-output-1`,
-      });
-    }
-
-    const inNewRow = (index + 1) % 5 === 0;
-
-    position.x = inNewRow ? 50 : position.x + 280;
-    position.y = inNewRow ? position.y + 150 : position.y;
-
-    nodes.push(node);
-  });
-
-  return {
-    edges,
-    nodes,
-  };
-}
-async function stopRecording() {
-  if (state.isGenerating) return;
-
-  try {
-    state.isGenerating = true;
-
-    if (state.flows.length !== 0) {
-      if (state.workflowId) {
-        const workflow = workflowStore.getById(state.workflowId);
-        const startBlock = workflow.drawflow.nodes.find(
-          (node) => node.id === state.connectFrom.id
-        );
-        const updatedDrawflow = generateDrawflow(state.connectFrom, startBlock);
-
-        const drawflow = {
-          ...workflow.drawflow,
-          nodes: [...workflow.drawflow.nodes, ...updatedDrawflow.nodes],
-          edges: [...workflow.drawflow.edges, ...updatedDrawflow.edges],
-        };
-
-        await workflowStore.update({
-          id: state.workflowId,
-          data: { drawflow },
-        });
-      } else {
-        const drawflow = generateDrawflow();
-
-        await workflowStore.insert({
-          drawflow,
-          name: state.name,
-        });
-      }
-    }
-
-    await browser.storage.local.remove(['isRecording', 'recording']);
-    await browser.browserAction.setBadgeText({ text: '' });
-
-    const tabs = (await browser.tabs.query({})).filter((tab) =>
-      tab.url.startsWith('http')
-    );
-    Promise.allSettled(
-      tabs.map(({ id }) =>
-        browser.tabs.sendMessage(id, { type: 'recording:stop' })
-      )
-    );
-
-    state.isGenerating = false;
-
-    router.push('/');
-  } catch (error) {
-    state.isGenerating = false;
-    console.error(error);
-  }
-}
-function removeBlock(index) {
-  state.flows.splice(index, 1);
-
-  browser.storage.local.set({ recording: toRaw(state) });
-}
-
-onMounted(async () => {
-  const { recording, isRecording } = await browser.storage.local.get([
-    'recording',
-    'isRecording',
-  ]);
-
-  if (!isRecording && !recording) return;
-
-  Object.assign(state, recording);
-});
-</script>

+ 0 - 6
src/popup/router.js

@@ -1,6 +1,5 @@
 import { createRouter, createWebHashHistory } from 'vue-router';
 import Home from './pages/Home.vue';
-import Recording from './pages/Recording.vue';
 
 const routes = [
   {
@@ -8,11 +7,6 @@ const routes = [
     name: 'home',
     component: Home,
   },
-  {
-    path: '/recording',
-    name: 'recording',
-    component: Recording,
-  },
 ];
 
 export default createRouter({

+ 1 - 1
src/sandbox/utils/handleBlockExpression.js

@@ -1,5 +1,5 @@
 import * as tmpl from '@n8n_io/riot-tmpl';
-import functions from '@/newtab/workflowEngine/templating/templatingFunctions';
+import functions from '@/workflowEngine/templating/templatingFunctions';
 
 tmpl.brackets.set('{{ }}');
 

+ 3 - 42
src/stores/hostedWorkflow.js

@@ -1,12 +1,11 @@
 import { defineStore } from 'pinia';
 import browser from 'webextension-polyfill';
 import { fetchApi } from '@/utils/api';
-import { findTriggerBlock } from '@/utils/helper';
 import {
   registerWorkflowTrigger,
   cleanWorkflowTriggers,
 } from '@/utils/workflowTrigger';
-import { useUserStore } from './user';
+import { findTriggerBlock } from '@/utils/helper';
 
 export const useHostedWorkflowStore = defineStore('hosted-workflows', {
   storageMap: {
@@ -31,10 +30,10 @@ export const useHostedWorkflowStore = defineStore('hosted-workflows', {
     async insert(data, idKey = 'hostId') {
       if (Array.isArray(data)) {
         data.forEach((item) => {
-          this.workflows[item[idKey]] = item;
+          this.workflows[idKey] = item;
         });
       } else {
-        this.workflows[data[idKey]] = data;
+        this.workflows[idKey] = data;
       }
 
       await this.saveToStorage('workflows');
@@ -57,44 +56,6 @@ export const useHostedWorkflowStore = defineStore('hosted-workflows', {
 
       return this.workflows[id];
     },
-    async addHostedWorkflow(hostId) {
-      if (this.workflows[hostId]) throw new Error('exist');
-
-      const userStore = useUserStore();
-      if (!userStore.user && this.toArray.length >= 3)
-        throw new Error('rate-exceeded');
-
-      const isTheUserHost = userStore.getHostedWorkflows.some(
-        (host) => hostId === host.hostId
-      );
-      if (isTheUserHost) throw new Error('exist');
-
-      const response = await fetchApi('/workflows/hosted', {
-        method: 'POST',
-        body: JSON.stringify({ hostId }),
-      });
-      const result = await response.json();
-
-      if (!response.ok) {
-        const error = new Error(result.message);
-        error.data = result.data;
-
-        throw error;
-      }
-
-      if (result === null) throw new Error('not-found');
-
-      result.hostId = hostId;
-      result.createdAt = Date.now();
-
-      const triggerBlock = findTriggerBlock(result.drawflow);
-      await registerWorkflowTrigger(hostId, triggerBlock);
-
-      this.workflows[hostId] = result;
-      await this.saveToStorage('workflows');
-
-      return result;
-    },
     async fetchWorkflows(ids) {
       if (!ids || ids.length === 0) return null;
 

+ 1 - 1
src/stores/main.js

@@ -17,7 +17,7 @@ export const useStore = defineStore('main', {
       deleteLogAfter: 30,
       logsLimit: 1000,
       editor: {
-        minZoom: 0.6,
+        minZoom: 0.3,
         maxZoom: 1.3,
         arrow: true,
         snapToGrid: false,

+ 8 - 1
src/stores/workflow.js

@@ -48,6 +48,7 @@ const defaultWorkflow = (data = null, options = {}) => {
       debugMode: false,
       restartTimes: 3,
       notification: true,
+      execContext: 'popup',
       reuseLastState: false,
       inputAutocomplete: true,
       onError: 'stop-workflow',
@@ -93,13 +94,18 @@ export const useWorkflowStore = defineStore('workflow', {
   state: () => ({
     states: [],
     workflows: {},
+    popupStates: [],
     retrieved: false,
+    isFirstTime: false,
   }),
   getters: {
+    getAllStates: (state) => [...state.popupStates, ...state.states],
     getById: (state) => (id) => state.workflows[id],
     getWorkflows: (state) => Object.values(state.workflows),
     getWorkflowStates: (state) => (id) =>
-      state.states.filter(({ workflowId }) => workflowId === id),
+      [...state.states, ...state.popupStates].filter(
+        ({ workflowId }) => workflowId === id
+      ),
   },
   actions: {
     async loadData() {
@@ -120,6 +126,7 @@ export const useWorkflowStore = defineStore('workflow', {
         });
       }
 
+      this.isFirstTime = isFirstTime;
       this.workflows = convertWorkflowsToObject(localWorkflows);
 
       this.retrieved = true;

+ 1 - 1
src/utils/shared.js

@@ -345,7 +345,7 @@ export const tasks = {
       regex: '',
       prefixText: '',
       suffixText: '',
-      regexExp: ['g'],
+      regexExp: [],
       dataColumn: '',
       saveData: true,
       includeTags: false,

+ 4 - 2
src/utils/testConditions.js

@@ -1,6 +1,6 @@
 import cloneDeep from 'lodash.clonedeep';
 import objectPath from 'object-path';
-import renderString from '@/newtab/workflowEngine/templating/renderString';
+import renderString from '@/workflowEngine/templating/renderString';
 import { conditionBuilder } from './shared';
 
 const isBoolStr = (str) => {
@@ -59,7 +59,8 @@ export default async function (conditionsArr, workflowData) {
     for (const key of Object.keys(data)) {
       const { value, list } = await renderString(
         copyData[key],
-        workflowData.refData
+        workflowData.refData,
+        workflowData.isPopup
       );
 
       copyData[key] = value ?? '';
@@ -83,6 +84,7 @@ export default async function (conditionsArr, workflowData) {
       } else {
         conditionValue = await workflowData.checkCodeCondition({
           data: copyData,
+          isPopup: workflowData.isPopup,
           refData: workflowData.refData,
         });
       }

+ 17 - 3
src/utils/workflowData.js

@@ -16,15 +16,29 @@ const requiredPermissions = {
   trigger: {
     name: contextMenuPermission,
     hasPermission({ data }) {
-      if (data.type !== 'context-menu') return true;
+      const permissions = [];
 
-      return checkPermission([contextMenuPermission]);
+      if (data.triggers) {
+        data.triggers.forEach((trigger) => {
+          if (trigger.type !== 'context-menu') return;
+
+          permissions.push(contextMenuPermission);
+        });
+      } else if (data.type === 'context-menu') {
+        permissions.push(contextMenuPermission);
+      }
+
+      return checkPermission(permissions);
     },
   },
   clipboard: {
     name: 'clipboardRead',
     hasPermission() {
-      return checkPermission(['clipboardRead']);
+      const clipboardPermissions = ['clipboardRead'];
+      if (BROWSER_TYPE === 'firefox')
+        clipboardPermissions.push('clipboardWrite');
+
+      return checkPermission(clipboardPermissions);
     },
   },
   notification: {

+ 2 - 1
src/newtab/workflowEngine/WorkflowEngine.js → src/workflowEngine/WorkflowEngine.js

@@ -9,11 +9,12 @@ import WorkflowWorker from './WorkflowWorker';
 let blocks = getBlocks();
 
 class WorkflowEngine {
-  constructor(workflow, { states, logger, blocksHandler, options }) {
+  constructor(workflow, { states, logger, blocksHandler, isPopup, options }) {
     this.id = nanoid();
     this.states = states;
     this.logger = logger;
     this.workflow = workflow;
+    this.isPopup = isPopup ?? true;
     this.blocksHandler = blocksHandler;
     this.parentWorkflow = options?.parentWorkflow;
     this.saveLog = workflow.settings?.saveLog ?? true;

+ 0 - 0
src/newtab/workflowEngine/WorkflowLogger.js → src/workflowEngine/WorkflowLogger.js


+ 0 - 0
src/newtab/workflowEngine/WorkflowState.js → src/workflowEngine/WorkflowState.js


+ 4 - 1
src/newtab/workflowEngine/WorkflowWorker.js → src/workflowEngine/WorkflowWorker.js

@@ -175,6 +175,7 @@ class WorkflowWorker {
     const replacedBlock = await templating({
       block,
       data: refData,
+      isPopup: this.engine.isPopup,
       refKeys:
         isRetry || block.data.disableBlock
           ? null
@@ -248,7 +249,9 @@ class WorkflowWorker {
 
         if (blockOnError.insertData) {
           for (const item of blockOnError.dataToInsert) {
-            let value = await renderString(item.value, refData)?.value;
+            let value = (
+              await renderString(item.value, refData, this.engine.isPopup)
+            )?.value;
             value = parseJSON(value, value);
 
             if (item.type === 'variable') {

+ 5 - 12
src/newtab/workflowEngine/blocksHandler.js → src/workflowEngine/blocksHandler.js

@@ -1,20 +1,13 @@
 import customHandlers from '@business/blocks/backgroundHandler';
 import { toCamelCase } from '@/utils/helper';
 
-const blocksHandler = require.context(
-  './blocksHandler',
-  false,
-  /\.js$/,
-  'lazy'
-);
-const handlers = {};
-
-blocksHandler.keys().forEach((key) => {
+const blocksHandler = require.context('./blocksHandler', false, /\.js$/);
+const handlers = blocksHandler.keys().reduce((acc, key) => {
   const name = key.replace(/^\.\/handler|\.js/g, '');
 
-  blocksHandler(key).then((module) => {
-    handlers[toCamelCase(name)] = module.default;
-  });
+  acc[toCamelCase(name)] = blocksHandler(key).default;
+
+  return acc;
 }, {});
 
 export default function () {

+ 34 - 4
src/newtab/workflowEngine/blocksHandler/handlerActiveTab.js → src/workflowEngine/blocksHandler/handlerActiveTab.js

@@ -15,14 +15,44 @@ async function activeTab(block) {
       return data;
     }
 
+    const minimizeDashboard = async (currentWindow) => {
+      if (currentWindow.type !== 'popup') return;
+
+      const [tab] = currentWindow.tabs;
+      const isDashboard = tab && tab.url.includes(browser.runtime.getURL(''));
+      const isWindowFocus =
+        currentWindow.focused || currentWindow.state === 'maximized';
+
+      if (isWindowFocus && isDashboard) {
+        const windowOptions = { focused: false };
+        if (currentWindow.state === 'maximized')
+          windowOptions.state = 'minimized';
+
+        await browser.windows.update(currentWindow.id, windowOptions);
+      }
+    };
+
+    if (this.engine.isPopup) {
+      const currentWindow = await browser.windows.getCurrent({
+        populate: true,
+      });
+      await minimizeDashboard(currentWindow);
+    } else {
+      const allWindows = await browser.windows.getAll({ populate: true });
+      for (const currWindow of allWindows) {
+        await minimizeDashboard(currWindow);
+      }
+    }
+
+    await sleep(500);
+
     const [tab] = await browser.tabs.query({
       active: true,
-      url: '*://*/*',
+      lastFocusedWindow: true,
     });
-
-    if (!tab?.url.startsWith('http')) {
+    if (!tab || !tab?.url.startsWith('http')) {
       const error = new Error('invalid-active-tab');
-      error.data = { url: tab.url };
+      error.data = { url: tab?.url };
 
       throw error;
     }

+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerBlockPackage.js → src/workflowEngine/blocksHandler/handlerBlockPackage.js


+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerBlocksGroup.js → src/workflowEngine/blocksHandler/handlerBlocksGroup.js


+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerBrowserEvent.js → src/workflowEngine/blocksHandler/handlerBrowserEvent.js


+ 3 - 0
src/newtab/workflowEngine/blocksHandler/handlerClipboard.js → src/workflowEngine/blocksHandler/handlerClipboard.js

@@ -21,6 +21,9 @@ function doCommand(command, value) {
 }
 
 export default async function ({ data, id, label }) {
+  if (!this.engine.isPopup)
+    throw new Error('Clipboard block is not supported in background execution');
+
   const hasPermission = await browser.permissions.contains({
     permissions: ['clipboardRead'],
   });

+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerCloseTab.js → src/workflowEngine/blocksHandler/handlerCloseTab.js


+ 57 - 11
src/newtab/workflowEngine/blocksHandler/handlerConditions.js → src/workflowEngine/blocksHandler/handlerConditions.js

@@ -3,9 +3,10 @@ import browser from 'webextension-polyfill';
 import compareBlockValue from '@/utils/compareBlockValue';
 import testConditions from '@/utils/testConditions';
 import renderString from '../templating/renderString';
-import { automaRefDataStr, messageSandbox } from '../helper';
+import { automaRefDataStr, messageSandbox, checkCSPAndInject } from '../helper';
 
 const nanoid = customAlphabet('1234567890abcdef', 5);
+const isMV2 = browser.runtime.getManifest().manifest_version === 2;
 
 function checkConditions(data, conditionOptions) {
   return new Promise((resolve, reject) => {
@@ -46,21 +47,54 @@ function checkConditions(data, conditionOptions) {
   });
 }
 async function checkCodeCondition(activeTab, payload) {
-  const variableId = nanoid();
+  const variableId = `automa${nanoid()}`;
 
-  if (!payload.data.context || payload.data.context === 'website') {
+  if (
+    !payload.data.context ||
+    payload.data.context === 'website' ||
+    !payload.isPopup
+  ) {
     if (!activeTab.id) throw new Error('no-tab');
 
+    const refDataScriptStr = automaRefDataStr(variableId);
+
+    if (!isMV2) {
+      const result = await checkCSPAndInject(
+        {
+          target: { tabId: activeTab.id },
+          debugMode: payload.debugMode,
+        },
+        () => {
+          return `
+          (async () => {
+            const ${variableId} = ${JSON.stringify(payload.refData)};
+            ${refDataScriptStr}
+            try {
+              ${payload.data.code}
+            } catch (error) {
+              return {
+                $isError: true,
+                message: error.message,
+              }
+            }
+          })();
+        `;
+        }
+      );
+
+      if (result.isBlocked) return result.value;
+    }
+
     const [{ result }] = await browser.scripting.executeScript({
       world: 'MAIN',
-      args: [payload, variableId, automaRefDataStr(variableId)],
+      args: [payload, variableId, refDataScriptStr],
       target: {
         tabId: activeTab.id,
         frameIds: [activeTab.frameId || 0],
       },
       func: ({ data, refData }, varId, refDataScript) => {
         return new Promise((resolve, reject) => {
-          const varName = `automa${varId}`;
+          const varName = varId;
 
           const scriptEl = document.createElement('script');
           scriptEl.textContent = `
@@ -128,12 +162,17 @@ async function conditions({ data, id }, { prevBlockData, refData }) {
     ? prevBlockData[0]
     : prevBlockData;
 
+  const { debugMode } = this.engine.workflow?.settings || {};
+
   if (condition && condition.conditions) {
     const conditionPayload = {
+      isMV2,
       refData,
-      isMV2: this.engine.isMV2,
-      checkCodeCondition: (payload) =>
-        checkCodeCondition(this.activeTab, payload),
+      isPopup: this.engine.isPopup,
+      checkCodeCondition: (payload) => {
+        payload.debugMode = debugMode;
+        return checkCodeCondition(this.activeTab, payload);
+      },
       sendMessage: (payload) =>
         this._sendMessageToTab({ ...payload.data, label: 'conditions', id }),
     };
@@ -151,9 +190,16 @@ async function conditions({ data, id }, { prevBlockData, refData }) {
     for (const { type, value, compareValue, id: itemId } of data.conditions) {
       if (isConditionMet) break;
 
-      const firstValue = await renderString(compareValue ?? prevData, refData)
-        .value;
-      const secondValue = await renderString(value, refData).value;
+      const firstValue = (
+        await renderString(
+          compareValue ?? prevData,
+          refData,
+          this.engine.isPopup
+        )
+      ).value;
+      const secondValue = (
+        await renderString(value, refData, this.engine.isPopup)
+      ).value;
 
       Object.assign(replacedValue, firstValue.list, secondValue.list);
 

+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerCookie.js → src/workflowEngine/blocksHandler/handlerCookie.js


+ 130 - 0
src/workflowEngine/blocksHandler/handlerCreateElement.js

@@ -0,0 +1,130 @@
+import { customAlphabet } from 'nanoid/non-secure';
+import browser from 'webextension-polyfill';
+import { automaRefDataStr, checkCSPAndInject } from '../helper';
+
+const nanoid = customAlphabet('1234567890abcdef', 5);
+
+function getAutomaScript(refData) {
+  const varName = `automa${nanoid()}`;
+
+  const str = `
+const ${varName} = ${JSON.stringify(refData)};
+${automaRefDataStr(varName)}
+function automaSetVariable(name, value) {
+  ${varName}.variables[name] = value;
+}
+function automaExecWorkflow(options = {}) {
+  window.dispatchEvent(new CustomEvent('automa:execute-workflow', { detail: options }));
+}
+  `;
+
+  return str;
+}
+
+async function handleCreateElement(block, { refData }) {
+  if (!this.activeTab.id) throw new Error('no-tab');
+
+  const { data } = block;
+  const preloadScriptsPromise = await Promise.allSettled(
+    data.preloadScripts.map((item) => {
+      if (!item.src.startsWith('http'))
+        return Promise.reject(new Error('Invalid URL'));
+
+      return fetch(item.src)
+        .then((response) => response.text())
+        .then((result) => ({ type: item.type, script: result }));
+    })
+  );
+  const preloadScripts = preloadScriptsPromise.reduce((acc, item) => {
+    if (item.status === 'rejected') return acc;
+
+    acc.push(item.value);
+
+    return acc;
+  }, []);
+
+  data.preloadScripts = preloadScripts;
+
+  const payload = {
+    ...block,
+    data,
+    preloadCSS: data.preloadScripts.filter((item) => item.type === 'style'),
+  };
+
+  if (data.javascript && !this.engine.isMV2) {
+    payload.data.dontInjectJS = true;
+    payload.data.automaScript = getAutomaScript({ ...refData, secrets: {} });
+  }
+
+  await this._sendMessageToTab(payload, {}, data.runBeforeLoad ?? false);
+
+  if (data.javascript && !this.engine.isMV2) {
+    const target = {
+      tabId: this.activeTab.id,
+      frameIds: [this.activeTab.frameId || 0],
+    };
+
+    const { debugMode } = this.engine.workflow?.settings || {};
+    const result = await checkCSPAndInject(
+      {
+        target,
+        debugMode,
+        options: {
+          awaitPromise: false,
+          returnByValue: false,
+        },
+      },
+      () => {
+        let jsPreload = '';
+        preloadScripts.forEach((item) => {
+          if (item.type === 'style') return;
+
+          jsPreload += `${item.script}\n`;
+        });
+
+        const automaScript = payload.data?.automaScript || '';
+
+        return `(() => { ${jsPreload} \n ${automaScript}\n${data.javascript} })()`;
+      }
+    );
+
+    if (!result.isBlocked) {
+      await browser.scripting.executeScript({
+        world: 'MAIN',
+        target,
+        args: [
+          data.javascript,
+          block.id,
+          payload.data?.automaScript || '',
+          preloadScripts,
+        ],
+        func: (code, blockId, $automaScript, $preloadScripts) => {
+          const baseId = `automa-${blockId}`;
+
+          $preloadScripts.forEach((item) => {
+            if (item.type === 'style') return;
+
+            const script = document.createElement(item.type);
+            script.id = `${baseId}-script`;
+            script.textContent = item.script;
+
+            document.body.appendChild(script);
+          });
+
+          const script = document.createElement('script');
+          script.id = `${baseId}-javascript`;
+          script.textContent = `(() => { ${$automaScript}\n${code} })()`;
+
+          document.body.appendChild(script);
+        },
+      });
+    }
+  }
+
+  return {
+    data: '',
+    nextBlockId: this.getBlockConnections(block.id),
+  };
+}
+
+export default handleCreateElement;

+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerDataMapping.js → src/workflowEngine/blocksHandler/handlerDataMapping.js


+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerDelay.js → src/workflowEngine/blocksHandler/handlerDelay.js


+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerDeleteData.js → src/workflowEngine/blocksHandler/handlerDeleteData.js


+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerElementExists.js → src/workflowEngine/blocksHandler/handlerElementExists.js


+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerExecuteWorkflow.js → src/workflowEngine/blocksHandler/handlerExecuteWorkflow.js


+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerExportData.js → src/workflowEngine/blocksHandler/handlerExportData.js


+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerForwardPage.js → src/workflowEngine/blocksHandler/handlerForwardPage.js


+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerGoBack.js → src/workflowEngine/blocksHandler/handlerGoBack.js


+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerGoogleSheets.js → src/workflowEngine/blocksHandler/handlerGoogleSheets.js


+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerHandleDialog.js → src/workflowEngine/blocksHandler/handlerHandleDialog.js


+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerHandleDownload.js → src/workflowEngine/blocksHandler/handlerHandleDownload.js


+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerHoverElement.js → src/workflowEngine/blocksHandler/handlerHoverElement.js


+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerIncreaseVariable.js → src/workflowEngine/blocksHandler/handlerIncreaseVariable.js


+ 10 - 2
src/newtab/workflowEngine/blocksHandler/handlerInsertData.js → src/workflowEngine/blocksHandler/handlerInsertData.js

@@ -10,7 +10,11 @@ async function insertData({ id, data }, { refData }) {
     let value = '';
 
     if (item.isFile) {
-      const replacedPath = await renderString(item.filePath || '', refData);
+      const replacedPath = await renderString(
+        item.filePath || '',
+        refData,
+        this.engine.isPopup
+      );
       const path = replacedPath.value;
       const isJSON = path.endsWith('.json');
       const isCSV = path.endsWith('.csv');
@@ -35,7 +39,11 @@ async function insertData({ id, data }, { refData }) {
       value = result;
       Object.assign(replacedValueList, replacedPath.list);
     } else {
-      const replacedValue = await renderString(item.value, refData);
+      const replacedValue = await renderString(
+        item.value,
+        refData,
+        this.engine.isPopup
+      );
       value = parseJSON(replacedValue.value, replacedValue.value);
       Object.assign(replacedValueList, replacedValue.list);
     }

+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerInteractionBlock.js → src/workflowEngine/blocksHandler/handlerInteractionBlock.js


+ 57 - 8
src/newtab/workflowEngine/blocksHandler/handlerJavascriptCode.js → src/workflowEngine/blocksHandler/handlerJavascriptCode.js

@@ -4,12 +4,18 @@ import cloneDeep from 'lodash.clonedeep';
 import {
   jsContentHandler,
   automaFetchClient,
-} from '@/newtab/utils/javascriptBlockUtil';
-import { messageSandbox, automaRefDataStr, waitTabLoaded } from '../helper';
+  jsContentHandlerEval,
+} from '../utils/javascriptBlockUtil';
+import {
+  waitTabLoaded,
+  messageSandbox,
+  automaRefDataStr,
+  checkCSPAndInject,
+} from '../helper';
 
 const nanoid = customAlphabet('1234567890abcdef', 5);
 
-function getAutomaScript(varName, refData, everyNewTab) {
+function getAutomaScript(varName, refData, everyNewTab, isEval = false) {
   let str = `
 const ${varName} = ${JSON.stringify(refData)};
 ${automaRefDataStr(varName)}
@@ -17,10 +23,27 @@ 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} } }));
+  if (${isEval}) {
+    $automaResolve({
+      columns: { 
+        data, 
+        insert,
+      }, 
+      variables: ${varName}.variables, 
+    });
+  } else{
+    document.body.dispatchEvent(new CustomEvent('__automa-next-block__', { detail: { data, insert, refData: ${varName} } }));
+  }
 }
 function automaResetTimeout() {
- document.body.dispatchEvent(new CustomEvent('__automa-reset-timeout__'));
+  if (${isEval}) {
+    clearTimeout($automaTimeout);
+    $automaTimeout = setTimeout(() => {
+      resolve();
+    }, $automaTimeoutMs);
+  } else {
+    document.body.dispatchEvent(new CustomEvent('__automa-reset-timeout__'));
+  }
 }
 function automaFetch(type, resource) {
   return (${automaFetchClient.toString()})('${varName}', { type, resource });
@@ -32,7 +55,11 @@ function automaFetch(type, resource) {
   return str;
 }
 async function executeInWebpage(args, target, worker) {
-  if (worker.engine.isMV2 || BROWSER_TYPE === 'firefox') {
+  if (!target.tabId) {
+    throw new Error('no-tab');
+  }
+
+  if (worker.engine.isMV2) {
     args[0] = cloneDeep(args[0]);
 
     const result = await worker._sendMessageToTab({
@@ -43,6 +70,25 @@ async function executeInWebpage(args, target, worker) {
     return result;
   }
 
+  const { debugMode } = worker.engine.workflow.settings;
+  const cspResult = await checkCSPAndInject({ target, debugMode }, () => {
+    const { 0: blockData, 1: preloadScripts, 3: varName } = args;
+    const automaScript = getAutomaScript(
+      varName,
+      blockData.refData,
+      blockData.data.everyNewTab,
+      true
+    );
+    const jsCode = jsContentHandlerEval({
+      blockData,
+      automaScript,
+      preloadScripts,
+    });
+
+    return jsCode;
+  });
+  if (cspResult.isBlocked) return cspResult.value;
+
   const [{ result }] = await browser.scripting.executeScript({
     args,
     target,
@@ -102,7 +148,7 @@ export async function javascriptCode({ outputs, data, ...block }, { refData }) {
 
   const instanceId = `automa${nanoid()}`;
   const automaScript =
-    data.everyNewTab || data.context === 'background'
+    data.everyNewTab && (!data.context || data.context !== 'background')
       ? ''
       : getAutomaScript(instanceId, payload.refData, data.everyNewTab);
 
@@ -113,7 +159,10 @@ export async function javascriptCode({ outputs, data, ...block }, { refData }) {
     });
   }
 
-  const inSandbox = BROWSER_TYPE !== 'firefox' && data.context === 'background';
+  const inSandbox =
+    BROWSER_TYPE !== 'firefox' &&
+    data.context === 'background' &&
+    this.engine.isPopup;
   const result = await (inSandbox
     ? messageSandbox('javascriptBlock', {
         instanceId,

+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerLogData.js → src/workflowEngine/blocksHandler/handlerLogData.js


+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerLoopBreakpoint.js → src/workflowEngine/blocksHandler/handlerLoopBreakpoint.js


+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerLoopData.js → src/workflowEngine/blocksHandler/handlerLoopData.js


+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerLoopElements.js → src/workflowEngine/blocksHandler/handlerLoopElements.js


+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerNewTab.js → src/workflowEngine/blocksHandler/handlerNewTab.js


+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerNewWindow.js → src/workflowEngine/blocksHandler/handlerNewWindow.js


+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerNotification.js → src/workflowEngine/blocksHandler/handlerNotification.js


+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerParameterPrompt.js → src/workflowEngine/blocksHandler/handlerParameterPrompt.js


+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerProxy.js → src/workflowEngine/blocksHandler/handlerProxy.js


+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerRegexVariable.js → src/workflowEngine/blocksHandler/handlerRegexVariable.js


+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerReloadTab.js → src/workflowEngine/blocksHandler/handlerReloadTab.js


+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerRepeatTask.js → src/workflowEngine/blocksHandler/handlerRepeatTask.js


+ 0 - 0
src/newtab/workflowEngine/blocksHandler/handlerSaveAssets.js → src/workflowEngine/blocksHandler/handlerSaveAssets.js


Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio