Browse Source

Merge branch 'dev' of https://github.com/automaapp/automa into cloud

Ahmad Kholid 3 years ago
parent
commit
673645f439

+ 33 - 76
src/background/WorkflowState.js

@@ -5,27 +5,13 @@ class WorkflowState {
     this.key = key;
     this.storage = storage;
 
-    this.cache = null;
+    this.states = new Map();
     this.eventListeners = {};
   }
 
-  async _updater(callback, event) {
-    try {
-      const storageStates = await this.get();
-      const states = callback(storageStates);
-
-      await this.storage.set(this.key, states);
-
-      if (event) {
-        this.dispatchEvent(event.name, event.params);
-      }
-
-      return states;
-    } catch (error) {
-      console.error(error);
-
-      return [];
-    }
+  _saveToStorage() {
+    const states = Object.fromEntries(this.states);
+    return this.storage.set(this.key, states);
   }
 
   dispatchEvent(name, params) {
@@ -53,77 +39,48 @@ class WorkflowState {
   }
 
   async get(stateId) {
-    try {
-      let states = this.cache ?? ((await this.storage.get(this.key)) || {});
-
-      if (Array.isArray(states)) {
-        states = {};
-        await this.storage.set(this.key, {});
-      }
-
-      if (typeof stateId === 'function') {
-        states = Object.values(states).find(stateId);
-      } else if (stateId) {
-        states = states[stateId];
-      } else {
-        this.cache = states;
-      }
-
-      return states;
-    } catch (error) {
-      console.error(error);
-
-      return null;
+    let { states } = this;
+
+    if (typeof stateId === 'function') {
+      states = Array.from(states.entries()).find(({ 1: state }) =>
+        stateId(state)
+      );
+    } else if (stateId) {
+      states = this.states.get(stateId);
     }
-  }
 
-  add(id, data = {}) {
-    return this._updater((states) => {
-      states[id] = {
-        id,
-        isPaused: false,
-        isDestroyed: false,
-        ...data,
-      };
+    return states;
+  }
 
-      return states;
-    });
+  async add(id, data = {}) {
+    this.states.set(id, data);
+    await this._saveToStorage(this.key);
   }
 
   async stop(id) {
-    await this.update(id, { isDestroyed: true });
+    const isStateExist = await this.get(id);
+    if (!isStateExist) {
+      await this.delete(id);
+      this.dispatchEvent('stop', id);
+      return id;
+    }
 
+    await this.update(id, { isDestroyed: true });
     this.dispatchEvent('stop', id);
-
     return id;
   }
 
-  update(id, data = {}) {
-    const event = {
-      name: 'update',
-      params: { id, data },
-    };
-
-    return this._updater((states) => {
-      if (states[id]) {
-        states[id] = { ...states[id], ...data };
-      }
-
-      return states;
-    }, event);
+  async update(id, data = {}) {
+    const state = this.states.get(id);
+    this.states.set(id, { ...state, ...data });
+    this.dispatchEvent('update', { id, data });
+    await this._saveToStorage();
   }
 
-  delete(id) {
-    const event = {
-      name: 'delete',
-      params: id,
-    };
-
-    return this._updater((states) => {
-      delete states[id];
-
-      return states;
-    }, event);
+  async delete(id) {
+    this.states.delete(id);
+    this.dispatchEvent('delete', id);
+    await this._saveToStorage();
   }
 }
 

+ 20 - 8
src/background/index.js

@@ -15,7 +15,7 @@ import blocksHandler from './workflowEngine/blocksHandler';
 import WorkflowLogger from './WorkflowLogger';
 
 const validateUrl = (str) => str?.startsWith('http');
-const storage = {
+const browserStorage = {
   async get(key) {
     try {
       const result = await browser.storage.local.get(key);
@@ -34,9 +34,21 @@ const storage = {
     }
   },
 };
+const localStateStorage = {
+  get(key) {
+    const data = parseJSON(localStorage.getItem(key), null);
+
+    return data;
+  },
+  set(key, value) {
+    const data = typeof value === 'object' ? JSON.stringify(value) : value;
+
+    return localStorage.setItem(key, data);
+  },
+};
 const workflow = {
-  states: new WorkflowState({ storage }),
-  logger: new WorkflowLogger({ storage }),
+  states: new WorkflowState({ storage: localStateStorage }),
+  logger: new WorkflowLogger({ storage: browserStorage }),
   async get(workflowId) {
     const { workflows, workflowHosts } = await browser.storage.local.get([
       'workflows',
@@ -126,7 +138,7 @@ async function checkWorkflowStates() {
   const states = await workflow.states.get();
   // const sessionStates = parseJSON(sessionStorage.getItem('workflowState'), {});
 
-  Object.values(states || {}).forEach((state) => {
+  states.forEach((state) => {
     /* Enable when using manifest 3 */
     // const resumeWorkflow =
     //   !state.isDestroyed && objectHasKey(sessionStates, state.id);
@@ -139,18 +151,18 @@ async function checkWorkflowStates() {
         });
       });
     } else {
-      delete states[state.id];
+      workflow.states.states.delete(state.id);
     }
   });
 
-  await storage.set('workflowState', states);
+  await browserStorage.set('workflowState', states);
 }
 checkWorkflowStates();
 async function checkVisitWebTriggers(tabId, tabUrl) {
   const workflowState = await workflow.states.get(({ state }) =>
     state.tabIds.includes(tabId)
   );
-  const visitWebTriggers = await storage.get('visitWebTriggers');
+  const visitWebTriggers = await browserStorage.get('visitWebTriggers');
   const triggeredWorkflow = visitWebTriggers?.find(({ url, isRegex, id }) => {
     if (url.trim() === '') return false;
 
@@ -168,7 +180,7 @@ async function checkVisitWebTriggers(tabId, tabUrl) {
 async function checkRecordingWorkflow(tabId, tabUrl) {
   if (!validateUrl(tabUrl)) return;
 
-  const isRecording = await storage.get('isRecording');
+  const isRecording = await browserStorage.get('isRecording');
   if (!isRecording) return;
 
   await browser.tabs.executeScript(tabId, {

+ 6 - 16
src/background/workflowEngine/engine.js

@@ -145,6 +145,7 @@ class WorkflowEngine {
 
     this.states
       .add(this.id, {
+        id: this.id,
         state: this.state,
         workflowId: this.workflow.id,
         parentState: this.parentWorkflow,
@@ -329,15 +330,18 @@ class WorkflowEngine {
 
   async updateState(data) {
     const state = {
-      ...this.state,
       ...data,
       tabIds: [],
       currentBlock: [],
+      name: this.workflow.name,
+      startedTimestamp: this.startedTimestamp,
     };
 
     this.workers.forEach((worker) => {
+      const { id, name } = worker.currentBlock;
+
+      state.currentBlock.push({ id, name });
       state.tabIds.push(worker.activeTab.id);
-      state.currentBlock.push(worker.currentBlock);
     });
 
     await this.states.update(this.id, { state });
@@ -359,20 +363,6 @@ class WorkflowEngine {
       listener
     );
   }
-
-  get state() {
-    const keys = ['columns', 'referenceData', 'startedTimestamp'];
-    const state = {
-      name: this.workflow.name,
-      icon: this.workflow.icon,
-    };
-
-    keys.forEach((key) => {
-      state[key] = this[key];
-    });
-
-    return state;
-  }
 }
 
 export default WorkflowEngine;

+ 5 - 20
src/components/newtab/shared/SharedWorkflowState.vue

@@ -29,12 +29,14 @@
     </div>
     <div class="divide-y bg-box-transparent divide-y px-4 rounded-lg">
       <div
-        v-for="block in getBlock()"
+        v-for="block in data.state.currentBlock"
         :key="block.id || block.name"
         class="flex items-center py-2"
       >
-        <v-remixicon :name="block.icon" />
-        <p class="flex-1 ml-2 mr-4 text-overflow">{{ block.name }}</p>
+        <v-remixicon :name="tasks[block.name].icon" />
+        <p class="flex-1 ml-2 mr-4 text-overflow">
+          {{ tasks[block.name].name }}
+        </p>
         <ui-spinner color="text-accent" size="20" />
       </div>
     </div>
@@ -69,23 +71,6 @@ const props = defineProps({
 
 const { t } = useI18n();
 
-function getBlock() {
-  const block = props.data.state.currentBlock;
-
-  if (!block) return [];
-
-  const blockArr = Array.isArray(block) ? block : [block];
-
-  return blockArr.map((item) => {
-    if (tasks[item.name] && item.outputs)
-      return {
-        ...tasks[item.name],
-        name: t(`workflow.blocks.${item.name}.name`),
-      };
-
-    return item;
-  });
-}
 function formatDate(date, format) {
   if (format === 'relative') return dayjs(date).fromNow();
 

+ 4 - 2
src/content/blocksHandler/handlerJavascriptCode.js

@@ -1,9 +1,11 @@
-import { nanoid } from 'nanoid/non-secure';
+import { customAlphabet } from 'nanoid/non-secure';
 import { sendMessage } from '@/utils/message';
 import { automaRefDataStr } from '../utils';
 
+const nanoid = customAlphabet('1234567890abcdef', 5);
+
 function getAutomaScript(refData, everyNewTab) {
-  const varName = `automa${nanoid(5)}`;
+  const varName = `automa${nanoid()}`;
 
   let str = `
 const ${varName} = ${JSON.stringify(refData)};

+ 4 - 2
src/content/handleTestCondition.js

@@ -1,8 +1,10 @@
-import { nanoid } from 'nanoid/non-secure';
+import { customAlphabet } from 'nanoid/non-secure';
 import { visibleInViewport, isXPath } from '@/utils/helper';
 import FindElement from '@/utils/FindElement';
 import { automaRefDataStr } from './utils';
 
+const nanoid = customAlphabet('1234567890abcdef', 5);
+
 function handleConditionElement({ data, type }) {
   const selectorType = isXPath(data.selector) ? 'xpath' : 'cssSelector';
 
@@ -44,7 +46,7 @@ function handleConditionElement({ data, type }) {
 }
 function injectJsCode({ data, refData }) {
   return new Promise((resolve, reject) => {
-    const varName = `automa${nanoid(5)}`;
+    const varName = `automa${nanoid()}`;
 
     const scriptEl = document.createElement('script');
     scriptEl.textContent = `

+ 17 - 10
src/newtab/App.vue

@@ -83,10 +83,12 @@
 import { ref, shallowReactive, computed } from 'vue';
 import { useStore } from 'vuex';
 import { useI18n } from 'vue-i18n';
+import { useRoute } from 'vue-router';
 import { compare } from 'compare-versions';
 import browser from 'webextension-polyfill';
 import { useTheme } from '@/composable/theme';
 import { loadLocaleMessages, setI18nLanguage } from '@/lib/vueI18n';
+import { parseJSON } from '@/utils/helper';
 import { fetchApi, getSharedWorkflows, getUserWorkflows } from '@/utils/api';
 import dayjs from '@/lib/dayjs';
 import Log from '@/models/log';
@@ -96,6 +98,7 @@ import AppSidebar from '@/components/newtab/app/AppSidebar.vue';
 const { t } = useI18n();
 const store = useStore();
 const theme = useTheme();
+const route = useRoute();
 
 theme.init();
 
@@ -251,16 +254,6 @@ function handleStorageChanged(change) {
       data: change.logs.newValue,
     });
   }
-
-  if (change.workflowState) {
-    store.commit('updateState', {
-      key: 'workflowState',
-      value: Object.values(change.workflowState.newValue || {}).filter(
-        ({ isDestroyed, parentState }) =>
-          !isDestroyed && !parentState?.isCollection
-      ),
-    });
-  }
 }
 function closeModal() {
   let value = true;
@@ -295,6 +288,20 @@ window.addEventListener('beforeunload', () => {
   browser.storage.onChanged.removeListener(handleStorageChanged);
 });
 
+const includeRoutes = ['home', 'workflows-details'];
+window.addEventListener('storage', ({ key, newValue }) => {
+  if (key !== 'workflowState' || !includeRoutes.includes(route.name)) return;
+
+  const states = parseJSON(newValue, {});
+  store.commit('updateState', {
+    key: 'workflowState',
+    value: Object.values(states).filter(
+      ({ isDestroyed, parentState }) =>
+        !isDestroyed && !parentState?.isCollection
+    ),
+  });
+});
+
 (async () => {
   try {
     const { isFirstTime } = await browser.storage.local.get('isFirstTime');