1
0
Эх сурвалжийг харах

feat: add cookie 🍪 block

Ahmad Kholid 3 жил өмнө
parent
commit
4ced1dabbd

+ 68 - 0
src/background/workflowEngine/blocksHandler/handlerCookie.js

@@ -0,0 +1,68 @@
+import browser from 'webextension-polyfill';
+
+function getValues(data, keys) {
+  const values = {};
+  keys.forEach((key) => {
+    const value = data[key];
+
+    if (!value && typeof value !== 'boolean') return;
+
+    values[key] = value;
+  });
+
+  return values;
+}
+
+const keys = {
+  get: ['name', 'url'],
+  remove: ['name', 'url'],
+  getAll: ['domain', 'name', 'path', 'secure', 'url'],
+  set: [
+    'name',
+    'url',
+    'expirationDate',
+    'domain',
+    'path',
+    'sameSite',
+    'secure',
+    'url',
+    'value',
+    'httpOnly',
+  ],
+};
+
+async function cookie({ data }) {
+  const hasPermission = await browser.permissions.contains({
+    permissions: ['cookies'],
+  });
+
+  if (!hasPermission) {
+    const error = new Error('no-permission');
+    error.data = { permission: 'cookies' };
+
+    throw error;
+  }
+
+  let key = data.type;
+  if (key === 'get' && data.getAll) key = 'getAll';
+
+  const values = getValues(data, keys[key]);
+  if (values.expirationDate) {
+    values.expirationDate = Date.now() / 1000 + +values.expirationDate;
+  }
+
+  const result = await browser.cookies[key](values);
+
+  if (data.type === 'get') {
+    if (data.assignVariable) {
+      this.setVariable(data.variableName, result);
+    }
+    if (data.saveData) {
+      this.addDataToColumn(data.dataColumn, result);
+    }
+  }
+
+  return result;
+}
+
+export default cookie;

+ 4 - 1
src/background/workflowEngine/blocksHandler/handlerSaveAssets.js

@@ -19,7 +19,10 @@ export default async function ({ data, id, label }) {
   });
 
   if (!hasPermission) {
-    throw new Error('no-permission');
+    const error = new Error('no-permission');
+    error.data = { permission: 'downloads' };
+
+    throw error;
   }
 
   let sources = [data.url];

+ 1 - 0
src/components/newtab/shared/SharedPermissionsModal.vue

@@ -47,6 +47,7 @@ const emit = defineEmits(['update:modelValue', 'granted']);
 const { t } = useI18n();
 
 const icons = {
+  cookies: 'mdiCookieOutline',
   downloads: 'riDownloadLine',
   clipboardRead: 'riClipboardLine',
   contextMenus: 'riFileListLine',

+ 148 - 0
src/components/newtab/workflow/edit/EditCookie.vue

@@ -0,0 +1,148 @@
+<template>
+  <div>
+    <ui-textarea
+      :model-value="data.description"
+      class="w-full"
+      :placeholder="t('common.description')"
+      @change="updateData({ description: $event })"
+    />
+    <template v-if="permission.has.cookies">
+      <ui-select
+        :model-value="data.type"
+        class="w-full mt-4"
+        @change="updateData({ type: $event })"
+      >
+        <option v-for="type in types" :key="type" :value="type">
+          {{ t(`workflow.blocks.cookie.types.${type}`) }}
+        </option>
+      </ui-select>
+      <ui-checkbox
+        v-if="data.type === 'get'"
+        :model-value="data.getAll"
+        class="mt-1"
+        @change="updateData({ getAll: $event })"
+      >
+        {{ t('workflow.blocks.cookie.types.getAll') }}
+      </ui-checkbox>
+      <ui-input
+        :model-value="data.url"
+        class="mt-2 w-full"
+        type="url"
+        label="URL"
+        placeholder="https://example.com/"
+        @change="updateData({ url: $event })"
+      />
+      <ui-input
+        :model-value="data.name"
+        :label="`Name ${isOptional(isGetOrSet)}`"
+        class="mt-2 w-full"
+        placeholder="site-cookie"
+        @change="updateData({ name: $event })"
+      />
+      <ui-input
+        v-if="data.type === 'set'"
+        :model-value="data.value"
+        label="Value (optional)"
+        class="mt-2 w-full"
+        placeholder="value"
+        @change="updateData({ value: $event })"
+      />
+      <ui-input
+        :model-value="data.path"
+        class="mt-2 w-full"
+        label="Path (optional)"
+        placeholder="/"
+        @change="updateData({ path: $event })"
+      />
+      <ui-input
+        v-if="isGetOrSet"
+        :model-value="data.domain"
+        class="mt-2 w-full"
+        label="Domain (optional)"
+        placeholder=".example.com"
+        @change="updateData({ domain: $event })"
+      />
+      <ui-input
+        v-if="data.type === 'set'"
+        :model-value="data.sameSite"
+        class="mt-2 w-full"
+        label="sameSite (optional)"
+        placeholder="lax"
+        @change="updateData({ sameSite: $event })"
+      />
+      <ui-input
+        v-if="data.type === 'set'"
+        :model-value="data.expirationDate"
+        class="mt-2 w-full"
+        label="expirationDate (seconds) (optional)"
+        placeholder="3600"
+        @change="updateData({ expirationDate: $event })"
+      />
+      <div
+        v-if="data.type === 'set' || (data.type === 'get' && data.getAll)"
+        class="mt-4"
+      >
+        <ui-checkbox
+          v-if="data.type === 'set'"
+          :model-value="data.httpOnly"
+          class="mr-4"
+          @change="updateData({ httpOnly: $event })"
+        >
+          httpOnly
+        </ui-checkbox>
+        <ui-checkbox
+          :model-value="data.secure"
+          @change="updateData({ secure: $event })"
+        >
+          secure
+        </ui-checkbox>
+      </div>
+      <div v-if="data.type === 'get'" class="pt-4 border-t mt-4 cookie-data">
+        <insert-workflow-data :data="data" variables @update="updateData" />
+      </div>
+    </template>
+    <template v-else>
+      <p class="mt-4">
+        This block requires "Cookies" permission to work properly
+      </p>
+      <ui-button variant="accent" class="mt-2" @click="permission.request">
+        {{ t('workflow.blocks.clipboard.grantPermission') }}
+      </ui-button>
+    </template>
+  </div>
+</template>
+<script setup>
+import { computed } from 'vue';
+import { useI18n } from 'vue-i18n';
+import { useHasPermissions } from '@/composable/hasPermissions';
+import InsertWorkflowData from './InsertWorkflowData.vue';
+
+const props = defineProps({
+  data: {
+    type: Object,
+    default: () => ({}),
+  },
+});
+const emit = defineEmits(['update:data']);
+
+const { t } = useI18n();
+const permission = useHasPermissions(['cookies']);
+
+const types = ['get', 'set', 'remove'];
+const isOptional = (optional = false) => (optional ? '(optional)' : '');
+
+const isGetOrSet = computed(
+  () =>
+    (props.data.type === 'get' && props.data.getAll) ||
+    props.data.type === 'set'
+);
+
+function updateData(value) {
+  emit('update:data', { ...props.data, ...value });
+}
+</script>
+<style>
+.cookie-data .block-variable {
+  margin-top: 0;
+}
+</style>

+ 3 - 1
src/components/newtab/workflow/editor/EditorUsedCredentials.vue

@@ -52,6 +52,7 @@
 <script setup>
 import { ref, onMounted } from 'vue';
 import { useI18n } from 'vue-i18n';
+import objectPath from 'object-path';
 import { tasks } from '@/utils/shared';
 
 const props = defineProps({
@@ -76,7 +77,8 @@ function checkCredentials() {
     const usedCredentials = new Set();
 
     keys.forEach((key) => {
-      const match = data[key]?.match?.(regex);
+      const str = objectPath.get(data, key);
+      const match = str?.match?.(regex);
       if (!match || !match[1]) return;
 
       usedCredentials.add(match[1]);

+ 1 - 1
src/composable/hasPermissions.js

@@ -21,7 +21,7 @@ export function useHasPermissions(permissions) {
           handlePermission(permission, true);
         });
 
-        if (needReload) {
+        if (typeof needReload === 'boolean' && needReload) {
           alert('Automa needs to reload to make this feature work');
           browser.runtime.getBackgroundPage().then((background) => {
             background.location.reload();

+ 2 - 0
src/lib/vRemixicon.js

@@ -258,6 +258,8 @@ export const icons = {
     'M20.41,3C21.8,5.71 22.35,8.84 22,12C21.8,15.16 20.7,18.29 18.83,21L17.3,20C18.91,17.57 19.85,14.8 20,12C20.34,9.2 19.89,6.43 18.7,4L20.41,3M5.17,3L6.7,4C5.09,6.43 4.15,9.2 4,12C3.66,14.8 4.12,17.57 5.3,20L3.61,21C2.21,18.29 1.65,15.17 2,12C2.2,8.84 3.3,5.71 5.17,3M12.08,10.68L14.4,7.45H16.93L13.15,12.45L15.35,17.37H13.09L11.71,14L9.28,17.33H6.76L10.66,12.21L8.53,7.45H10.8L12.08,10.68Z',
   mdiRegex:
     'M16,16.92C15.67,16.97 15.34,17 15,17C14.66,17 14.33,16.97 14,16.92V13.41L11.5,15.89C11,15.5 10.5,15 10.11,14.5L12.59,12H9.08C9.03,11.67 9,11.34 9,11C9,10.66 9.03,10.33 9.08,10H12.59L10.11,7.5C10.3,7.25 10.5,7 10.76,6.76V6.76C11,6.5 11.25,6.3 11.5,6.11L14,8.59V5.08C14.33,5.03 14.66,5 15,5C15.34,5 15.67,5.03 16,5.08V8.59L18.5,6.11C19,6.5 19.5,7 19.89,7.5L17.41,10H20.92C20.97,10.33 21,10.66 21,11C21,11.34 20.97,11.67 20.92,12H17.41L19.89,14.5C19.7,14.75 19.5,15 19.24,15.24V15.24C19,15.5 18.75,15.7 18.5,15.89L16,13.41V16.92H16V16.92M5,19A2,2 0 0,1 7,17A2,2 0 0,1 9,19A2,2 0 0,1 7,21A2,2 0 0,1 5,19H5Z',
+  mdiCookieOutline:
+    'M20.87 10.5C20.6 10 20 10 20 10H18V9C18 8 17 8 17 8H15V7C15 6 14 6 14 6H13V4C13 3 12 3 12 3C7.03 3 3 7.03 3 12C3 16.97 7.03 21 12 21C16.97 21 21 16.97 21 12C21 11.5 20.96 11 20.87 10.5M11.32 18.96C12 18.82 12.5 18.22 12.5 17.5C12.5 16.67 11.83 16 11 16S9.5 16.67 9.5 17.5C9.5 18 9.76 18.47 10.16 18.74C7.54 18.04 5.5 15.81 5.09 13.12C5 12.61 5 12.11 5 11.62C5.07 12.39 5.71 13 6.5 13C7.33 13 8 12.33 8 11.5S7.33 10 6.5 10C5.82 10 5.25 10.46 5.07 11.08C5.47 8 7.91 5.5 11 5.07V6.5C11 7.33 11.67 8 12.5 8H13V8.5C13 9.33 13.67 10 14.5 10H16V10.5C16 11.33 16.67 12 17.5 12H19C19 16.08 15.5 19.36 11.32 18.96M9.5 9C8.67 9 8 8.33 8 7.5S8.67 6 9.5 6 11 6.67 11 7.5 10.33 9 9.5 9M13 12.5C13 13.33 12.33 14 11.5 14S10 13.33 10 12.5 10.67 11 11.5 11 13 11.67 13 12.5M18 14.5C18 15.33 17.33 16 16.5 16S15 15.33 15 14.5 15.67 13 16.5 13 18 13.67 18 14.5Z',
   mdiDrag:
     'M7,19V17H9V19H7M11,19V17H13V19H11M15,19V17H17V19H15M7,15V13H9V15H7M11,15V13H13V15H11M15,15V13H17V15H15M7,11V9H9V11H7M11,11V9H13V11H11M15,11V9H17V11H15M7,7V5H9V7H7M11,7V5H13V7H11M15,7V5H17V7H15Z',
   webhookIcon:

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

@@ -96,6 +96,16 @@
         "specificFlow": "Only continue a specific flow",
         "selectFlow": "Select flow"
       },
+      "cookie": {
+        "name": "Cookie",
+        "description": "Get, set, or remove cookies",
+        "types": {
+          "get": "Get cookies",
+          "set": "Set cookie",
+          "remove": "Remove cookies",
+          "getAll": "Get all cookies"
+        }
+      },
       "slice-variable": {
         "name": "Slice variable",
         "description": "Extracts a section of a variable value",

+ 0 - 1
src/locales/en/newtab.json

@@ -366,7 +366,6 @@
     "messages": {
       "url-empty": "URL is empty",
       "invalid-url": "URL is not valid",
-      "no-permission": "Don't have permission",
       "conditions-empty": "Conditions is empty",
       "invalid-proxy-host": "Invalid proxy host",
       "workflow-disabled": "Workflow is disabled",

+ 2 - 1
src/manifest.chrome.json

@@ -43,7 +43,8 @@
     "clipboardRead",
     "downloads",
     "contextMenus",
-    "notifications"
+    "notifications",
+    "cookies"
   ],
   "permissions": [
     "tabs",

+ 1 - 1
src/manifest.firefox.json

@@ -40,7 +40,7 @@
       "all_frames": false
     }
   ],
-  "optional_permissions": ["clipboardRead", "downloads", "notifications"],
+  "optional_permissions": ["clipboardRead", "downloads", "notifications", "cookies"],
   "permissions": [
     "tabs",
     "proxy",

+ 41 - 0
src/utils/shared.js

@@ -1210,6 +1210,47 @@ export const tasks = {
       selector: 'body',
     },
   },
+  cookie: {
+    name: 'Cookie',
+    description: 'Get, set, or remove cookies',
+    icon: 'mdiCookieOutline',
+    editComponent: 'EditCookie',
+    component: 'BlockBasic',
+    category: 'browser',
+    inputs: 1,
+    outputs: 1,
+    allowedInputs: true,
+    maxConnection: 1,
+    refDataKeys: [
+      'domain',
+      'expirationDate',
+      'path',
+      'sameSite',
+      'name',
+      'url',
+      'value',
+    ],
+    data: {
+      disableBlock: false,
+      description: '',
+      type: 'get',
+      getAll: false,
+      domain: '',
+      expirationDate: '',
+      path: '',
+      sameSite: '',
+      name: '',
+      url: '',
+      value: '',
+      httpOnly: false,
+      secure: false,
+      session: false,
+      assignVariable: false,
+      variableName: '',
+      saveData: true,
+      dataColumn: '',
+    },
+  },
 };
 
 export const categories = {

+ 6 - 0
src/utils/workflowData.js

@@ -39,6 +39,12 @@ const requiredPermissions = {
       return checkPermission(['downloads']);
     },
   },
+  cookie: {
+    name: 'cookies',
+    hasPermission() {
+      return checkPermission(['cookies']);
+    },
+  },
 };
 
 export async function getWorkflowPermissions(drawflow) {