Browse Source

feat: add scheduled trigger handler

Ahmad Kholid 3 years ago
parent
commit
ff863e75c6

+ 39 - 2
src/background/index.js

@@ -1,3 +1,4 @@
+import browser from 'webextension-polyfill';
 import { MessageListener } from '@/utils/message';
 import { MessageListener } from '@/utils/message';
 import WorkflowEngine from './workflow-engine';
 import WorkflowEngine from './workflow-engine';
 
 
@@ -5,14 +6,22 @@ chrome.runtime.onInstalled.addListener((details) => {
   if (details.reason === chrome.runtime.OnInstalledReason.INSTALL) {
   if (details.reason === chrome.runtime.OnInstalledReason.INSTALL) {
     chrome.storage.local.set({
     chrome.storage.local.set({
       workflows: [],
       workflows: [],
+      visitWebTriggers: [],
       tasks: [],
       tasks: [],
     });
     });
   }
   }
 });
 });
 
 
-const message = new MessageListener('background');
+function getWorkflow(workflowId) {
+  return new Promise((resolve) => {
+    browser.storage.local.get('workflows').then(({ workflows }) => {
+      const workflow = workflows.find(({ id }) => id === workflowId);
 
 
-message.on('workflow:execute', (workflow) => {
+      resolve(workflow);
+    });
+  });
+}
+function executeWorkflow(workflow) {
   try {
   try {
     const engine = new WorkflowEngine(workflow);
     const engine = new WorkflowEngine(workflow);
     console.log('execute');
     console.log('execute');
@@ -23,6 +32,34 @@ message.on('workflow:execute', (workflow) => {
     console.error(error);
     console.error(error);
     return error;
     return error;
   }
   }
+}
+
+browser.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
+  if (changeInfo.status === 'complete') {
+    const visitWebTriggers =
+      (await browser.storage.local.get('visitWebTriggers'))?.visitWebTriggers ??
+      [];
+    const trigger = visitWebTriggers.find(({ url, isRegex }) =>
+      tab.url.match(isRegex ? new RegExp(url, 'g') : url)
+    );
+
+    if (trigger) {
+      const workflow = await getWorkflow(trigger.id);
+
+      executeWorkflow(workflow);
+    }
+  }
+});
+browser.alarms.onAlarm.addListener(({ name }) => {
+  getWorkflow(name).then((workflow) => {
+    if (!workflow) return;
+    console.log(workflow, 'alarm');
+    executeWorkflow(workflow);
+  });
 });
 });
 
 
+const message = new MessageListener('background');
+
+message.on('workflow:execute', executeWorkflow);
+
 chrome.runtime.onMessage.addListener(message.listener());
 chrome.runtime.onMessage.addListener(message.listener());

+ 4 - 1
src/background/workflow-engine.js

@@ -82,7 +82,10 @@ class WorkflowEngine {
         : this.workflow.drawflow;
         : this.workflow.drawflow;
     const blocks = drawflowData?.drawflow.Home.data;
     const blocks = drawflowData?.drawflow.Home.data;
 
 
-    if (!blocks) return;
+    if (!blocks) {
+      console.error('No block is found');
+      return;
+    }
 
 
     const blocksArr = Object.values(blocks);
     const blocksArr = Object.values(blocks);
     const triggerBlock = blocksArr.find(({ name }) => name === 'trigger');
     const triggerBlock = blocksArr.find(({ name }) => name === 'trigger');

+ 1 - 1
src/components/block/BlockBase.vue

@@ -7,7 +7,7 @@
       <slot></slot>
       <slot></slot>
     </div>
     </div>
     <div
     <div
-      class="absolute bottom-0 transition-transform duration-300 pt-4 ml-1 menu"
+      class="absolute bottom-1 transition-transform duration-300 pt-4 ml-1 menu"
     >
     >
       <div class="bg-accent px-3 py-2 text-white rounded-lg flex items-center">
       <div class="bg-accent px-3 py-2 text-white rounded-lg flex items-center">
         <button v-if="!hideEdit" @click="$emit('edit')">
         <button v-if="!hideEdit" @click="$emit('edit')">

+ 27 - 6
src/components/newtab/workflow/edit/EditTrigger.vue

@@ -12,14 +12,14 @@
     class="w-full"
     class="w-full"
     @change="updateData({ type: $event })"
     @change="updateData({ type: $event })"
   >
   >
-    <option value="manual">Manual</option>
-    <option value="interval">Interval</option>
-    <option value="date">Date</option>
+    <option v-for="trigger in triggers" :key="trigger.id" :value="trigger.id">
+      {{ trigger.name }}
+    </option>
   </ui-select>
   </ui-select>
   <transition-expand mode="out-in">
   <transition-expand mode="out-in">
     <div v-if="data.type === 'interval'" class="flex items-center mt-1">
     <div v-if="data.type === 'interval'" class="flex items-center mt-1">
       <ui-input
       <ui-input
-        :model-value="data.interval || '60'"
+        :model-value="data.interval"
         type="number"
         type="number"
         class="w-full mr-2"
         class="w-full mr-2"
         label="Interval (minutes)"
         label="Interval (minutes)"
@@ -31,7 +31,7 @@
         "
         "
       />
       />
       <ui-input
       <ui-input
-        :model-value="data.delay || '5'"
+        :model-value="data.delay"
         type="number"
         type="number"
         class="w-full"
         class="w-full"
         label="Delay (minutes)"
         label="Delay (minutes)"
@@ -49,7 +49,7 @@
         class="w-full"
         class="w-full"
         type="date"
         type="date"
         placeholder="Date"
         placeholder="Date"
-        @change="updateDate"
+        @change="updateDate({ date: $event })"
       />
       />
       <ui-input
       <ui-input
         :model-value="data.time || '00:00'"
         :model-value="data.time || '00:00'"
@@ -59,6 +59,21 @@
         @change="updateData({ time: $event || '00:00' })"
         @change="updateData({ time: $event || '00:00' })"
       />
       />
     </div>
     </div>
+    <div v-else-if="data.type === 'visit-web'" class="mt-2">
+      <ui-input
+        :model-value="data.url"
+        placeholder="URL or Regex"
+        class="w-full"
+        @change="updateData({ url: $event })"
+      />
+      <ui-checkbox
+        :model-value="data.isUrlRegex"
+        class="mt-1"
+        @change="updateData({ isUrlRegex: $event })"
+      >
+        Use regex
+      </ui-checkbox>
+    </div>
   </transition-expand>
   </transition-expand>
 </template>
 </template>
 <script setup>
 <script setup>
@@ -73,6 +88,12 @@ const props = defineProps({
 });
 });
 const emit = defineEmits(['update:data']);
 const emit = defineEmits(['update:data']);
 
 
+const triggers = [
+  { id: 'manual', name: 'Manually' },
+  { id: 'interval', name: 'Interval' },
+  { id: 'date', name: 'On spesific date' },
+  { id: 'visit-web', name: 'When visit a website' },
+];
 const maxDate = dayjs().add(30, 'day').format('YYYY-MM-DD');
 const maxDate = dayjs().add(30, 'day').format('YYYY-MM-DD');
 const minDate = dayjs().format('YYYY-MM-DD');
 const minDate = dayjs().format('YYYY-MM-DD');
 
 

+ 8 - 1
src/manifest.json

@@ -14,6 +14,13 @@
   "icons": {
   "icons": {
     "128": "icon-128.png"
     "128": "icon-128.png"
   },
   },
-  "permissions": ["storage", "unlimitedStorage", "tabs", "activeTab", "http://*/*", "https://*/*"],
+  "permissions": [
+    "tabs",
+    "alarms",
+    "storage",
+    "unlimitedStorage",
+    "http://*/*",
+    "https://*/*"
+  ],
   "web_accessible_resources": ["content.styles.css", "icon-128.png", "icon-34.png"]
   "web_accessible_resources": ["content.styles.css", "icon-128.png", "icon-34.png"]
 }
 }

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

@@ -48,6 +48,7 @@ import {
   onUnmounted,
   onUnmounted,
 } from 'vue';
 } from 'vue';
 import { useRoute, useRouter, onBeforeRouteLeave } from 'vue-router';
 import { useRoute, useRouter, onBeforeRouteLeave } from 'vue-router';
+import browser from 'webextension-polyfill';
 import emitter from 'tiny-emitter/instance';
 import emitter from 'tiny-emitter/instance';
 import { sendMessage } from '@/utils/message';
 import { sendMessage } from '@/utils/message';
 import { debounce } from '@/utils/helper';
 import { debounce } from '@/utils/helper';
@@ -98,9 +99,72 @@ function updateWorkflow(data) {
     data,
     data,
   });
   });
 }
 }
+async function handleWorkflowTrigger({ data }) {
+  try {
+    const workflowAlarm = await browser.alarms.get(workflowId);
+    const visitWebTriggers =
+      (await browser.storage.local.get('visitWebTriggers')?.visitWebTriggers) ??
+      [];
+    let visitWebTriggerIndex = visitWebTriggers.findIndex(
+      (item) => item.id === workflowId
+    );
+
+    if (workflowAlarm) await browser.alarms.clear(workflowId);
+    if (visitWebTriggerIndex !== -1) {
+      visitWebTriggers.splice(visitWebTriggerIndex, 1);
+
+      visitWebTriggerIndex = -1;
+
+      await browser.storage.local.set({ visitWebTriggers });
+    }
+
+    if (['date', 'interval'].includes(data.type)) {
+      let alarmInfo;
+
+      if (data.type === 'date') {
+        alarmInfo = {
+          when: data.date ? new Date(data.date).getTime() : Date.now() + 60000,
+        };
+      } else {
+        console.log(workflowAlarm, 'workflow-alarm');
+        alarmInfo = {
+          periodInMinutes: data.interval,
+        };
+
+        if (data.delay > 0) alarmInfo.delayInMinutes = data.delay;
+      }
+
+      if (alarmInfo) await browser.alarms.create(workflowId, alarmInfo);
+    } else if (data.type === 'visit-web') {
+      const payload = {
+        id: workflowId,
+        url: data.url,
+        isRegex: data.isUrlRegex,
+      };
+
+      if (visitWebTriggerIndex === -1) {
+        visitWebTriggers.push(payload);
+      } else {
+        visitWebTriggers[visitWebTriggerIndex] = payload;
+      }
+      console.log(visitWebTriggers);
+      await browser.storage.local.set({ visitWebTriggers });
+    }
+  } catch (error) {
+    console.error(error);
+  }
+}
+/* to-do clear alarms and trigger storage when delete workflow */
 function saveWorkflow() {
 function saveWorkflow() {
   const data = editor.value.export();
   const data = editor.value.export();
+
   updateWorkflow({ drawflow: JSON.stringify(data) }).then(() => {
   updateWorkflow({ drawflow: JSON.stringify(data) }).then(() => {
+    const [triggerBlockId] = editor.value.getNodesFromName('trigger');
+
+    if (triggerBlockId) {
+      handleWorkflowTrigger(editor.value.getNodeFromId(triggerBlockId));
+    }
+
     state.isDataChanged = false;
     state.isDataChanged = false;
   });
   });
 }
 }

+ 7 - 0
src/utils/shared.js

@@ -1,5 +1,6 @@
 /* to-do screenshot, assets, tab loaded, opened tab, and close tab block? */
 /* to-do screenshot, assets, tab loaded, opened tab, and close tab block? */
 /* to-do add timeout and trying to exists-element? */
 /* to-do add timeout and trying to exists-element? */
+/* active-tab: execute workflow on active tab */
 
 
 export const tasks = {
 export const tasks = {
   trigger: {
   trigger: {
@@ -15,6 +16,12 @@ export const tasks = {
     data: {
     data: {
       description: '',
       description: '',
       type: 'manual',
       type: 'manual',
+      interval: 60,
+      delay: 5,
+      date: '',
+      time: '00:00',
+      url: '',
+      isUrlRegex: false,
     },
     },
   },
   },
   'event-click': {
   'event-click': {