| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379 |
- import browser from 'webextension-polyfill';
- import { nanoid } from 'nanoid';
- import cloneDeep from 'lodash.clonedeep';
- import findSelector from '@/lib/findSelector';
- import { sendMessage } from '@/utils/message';
- import automa from '@business';
- import { toCamelCase, isXPath } from '@/utils/helper';
- import handleSelector, {
- queryElements,
- getDocumentCtx,
- } from './handleSelector';
- import blocksHandler from './blocksHandler';
- import showExecutedBlock from './showExecutedBlock';
- import shortcutListener from './services/shortcutListener';
- import initCommandPalette from './commandPalette';
- // import elementObserver from './elementObserver';
- import { elementSelectorInstance } from './utils';
- const isMainFrame = window.self === window.top;
- function messageToFrame(frameElement, blockData) {
- return new Promise((resolve, reject) => {
- function onMessage({ data }) {
- if (data.type !== 'automa:block-execute-result') return;
- if (data.result?.$isError) {
- const error = new Error(data.result.message);
- error.data = data.result.data;
- reject(error);
- } else {
- resolve(data.result);
- }
- window.removeEventListener('message', onMessage);
- }
- window.addEventListener('message', onMessage);
- const messageId = `message:${nanoid(4)}`;
- browser.storage.local.set({ [messageId]: true }).then(() => {
- frameElement.contentWindow.postMessage(
- {
- messageId,
- type: 'automa:execute-block',
- blockData: { ...blockData, frameSelector: '' },
- },
- '*'
- );
- });
- });
- }
- async function executeBlock(data) {
- const removeExecutedBlock = showExecutedBlock(data, data.executedBlockOnWeb);
- if (data.data?.selector?.includes('|>')) {
- const selectorsArr = data.data.selector.split('|>');
- const selector = selectorsArr.pop();
- const frameSelector = selectorsArr.join('|>');
- const framElSelector = selectorsArr.pop();
- let findBy = data?.data?.findBy;
- if (!findBy) {
- findBy = isXPath(frameSelector) ? 'xpath' : 'cssSelector';
- }
- const documentCtx = getDocumentCtx(selectorsArr.join('|>'));
- const frameElement = await queryElements(
- {
- findBy,
- multiple: false,
- waitForSelector: 5000,
- selector: framElSelector,
- },
- documentCtx
- );
- const frameError = (message) => {
- const error = new Error(message);
- error.data = { selector: frameSelector };
- return error;
- };
- if (!frameElement) throw frameError('iframe-not-found');
- const isFrameEelement = ['IFRAME', 'FRAME'].includes(frameElement.tagName);
- if (!isFrameEelement) throw frameError('not-iframe');
- const { x, y } = frameElement.getBoundingClientRect();
- const iframeDetails = { x, y };
- if (isMainFrame) {
- iframeDetails.windowWidth = window.innerWidth;
- iframeDetails.windowHeight = window.innerHeight;
- }
- data.data.selector = selector;
- data.data.$frameRect = iframeDetails;
- data.data.$frameSelector = frameSelector;
- if (frameElement.contentDocument) {
- data.frameSelector = frameSelector;
- } else {
- const result = await messageToFrame(frameElement, data);
- return result;
- }
- }
- const handlers = blocksHandler();
- const handler = handlers[toCamelCase(data.name || data.label)];
- if (handler) {
- const result = await handler(data, { handleSelector });
- removeExecutedBlock();
- return result;
- }
- const error = new Error(`"${data.label}" doesn't have a handler`);
- console.error(error);
- throw error;
- }
- async function messageListener({ data, source }) {
- try {
- if (data.type === 'automa:get-frame' && isMainFrame) {
- let frameRect = { x: 0, y: 0 };
- document.querySelectorAll('iframe').forEach((iframe) => {
- if (iframe.contentWindow !== source) return;
- frameRect = iframe.getBoundingClientRect();
- });
- source.postMessage(
- {
- frameRect,
- type: 'automa:the-frame-rect',
- },
- '*'
- );
- return;
- }
- if (data.type === 'automa:execute-block') {
- const messageToken = await browser.storage.local.get(data.messageId);
- if (!data.messageId || !messageToken[data.messageId]) {
- window.top.postMessage(
- {
- result: {
- $isError: true,
- message: 'Block id is empty',
- data: {},
- },
- type: 'automa:block-execute-result',
- },
- '*'
- );
- return;
- }
- await browser.storage.local.remove(data.messageId);
- executeBlock(data.blockData)
- .then((result) => {
- window.top.postMessage(
- {
- result,
- type: 'automa:block-execute-result',
- },
- '*'
- );
- })
- .catch((error) => {
- console.error(error);
- window.top.postMessage(
- {
- result: {
- $isError: true,
- message: error.message,
- data: error.data || {},
- },
- type: 'automa:block-execute-result',
- },
- '*'
- );
- });
- }
- } catch (error) {
- console.error(error);
- }
- }
- (() => {
- if (window.isAutomaInjected) return;
- initCommandPalette();
- let contextElement = null;
- let $ctxLink = '';
- let $ctxMediaUrl = '';
- let $ctxTextSelection = '';
- window.isAutomaInjected = true;
- window.addEventListener('message', messageListener);
- window.addEventListener(
- 'contextmenu',
- ({ target }) => {
- contextElement = target;
- $ctxTextSelection = window.getSelection().toString();
- const tag = target.tagName;
- if (tag === 'A') {
- $ctxLink = target.href;
- } else {
- const closestUrl = target.closest('a');
- if (closestUrl) $ctxLink = closestUrl.href;
- }
- const getMediaSrc = (element) => {
- let mediaSrc = element.src || '';
- if (!mediaSrc.src) {
- const sourceEl = element.querySelector('source');
- if (sourceEl) mediaSrc = sourceEl.src;
- }
- return mediaSrc;
- };
- const mediaTags = ['AUDIO', 'VIDEO', 'IMG'];
- if (mediaTags.includes(tag)) {
- $ctxMediaUrl = getMediaSrc(target);
- } else {
- const closestMedia = target.closest('audio,video,img');
- if (closestMedia) $ctxMediaUrl = getMediaSrc(closestMedia);
- }
- },
- true
- );
- window.isAutomaInjected = true;
- window.addEventListener('message', messageListener);
- window.addEventListener('contextmenu', ({ target }) => {
- contextElement = target;
- $ctxTextSelection = window.getSelection().toString();
- });
- if (isMainFrame) {
- shortcutListener();
- // window.addEventListener('load', elementObserver);
- }
- automa('content');
- browser.runtime.onMessage.addListener(async (data) => {
- const asyncExecuteBlock = async (block) => {
- try {
- const res = await executeBlock(block);
- return res;
- } catch (error) {
- console.error(error);
- const elNotFound = error.message === 'element-not-found';
- const isLoopItem = data.data?.selector?.includes('automa-loop');
- if (!elNotFound || !isLoopItem) return Promise.reject(error);
- const findLoopEl = data.loopEls.find(({ url }) =>
- window.location.href.includes(url)
- );
- const blockData = { ...data.data, ...findLoopEl, multiple: true };
- const loopBlock = {
- ...data,
- onlyGenerate: true,
- data: blockData,
- };
- await blocksHandler().loopData(loopBlock);
- return executeBlock(block);
- }
- };
- if (data.isBlock) {
- const res = await asyncExecuteBlock(data);
- return res;
- }
- switch (data.type) {
- case 'input-workflow-params':
- window.initPaletteParams?.(data.data);
- return Boolean(window.initPaletteParams);
- case 'content-script-exists':
- return true;
- case 'automa-element-selector': {
- return elementSelectorInstance();
- }
- case 'context-element': {
- let $ctxElSelector = '';
- if (contextElement) {
- $ctxElSelector = findSelector(contextElement);
- contextElement = null;
- }
- if (!$ctxTextSelection) {
- $ctxTextSelection = window.getSelection().toString();
- }
- const cloneContextData = cloneDeep({
- $ctxLink,
- $ctxMediaUrl,
- $ctxElSelector,
- $ctxTextSelection,
- });
- $ctxLink = '';
- $ctxMediaUrl = '';
- $ctxElSelector = '';
- $ctxTextSelection = '';
- return cloneContextData;
- }
- default:
- return null;
- }
- });
- })();
- window.addEventListener('__automa-fetch__', (event) => {
- const { id, resource, type } = event.detail;
- const sendResponse = (payload) => {
- window.dispatchEvent(
- new CustomEvent(`__automa-fetch-response-${id}__`, {
- detail: { id, ...payload },
- })
- );
- };
- sendMessage('fetch', { type, resource }, 'background')
- .then((result) => {
- sendResponse({ isError: false, result });
- })
- .catch((error) => {
- sendResponse({ isError: true, result: error.message });
- });
- });
- window.addEventListener('DOMContentLoaded', async () => {
- const link = window.location.pathname;
- const isAutomaWorkflow = /.+\.automa\.json$/.test(link);
- if (!isAutomaWorkflow) return;
- const accept = window.confirm(
- 'Do you want to add this workflow into Automa?'
- );
- if (!accept) return;
- const workflow = JSON.parse(document.documentElement.innerText);
- const { workflows: workflowsStorage } = await browser.storage.local.get(
- 'workflows'
- );
- const workflowId = nanoid();
- const workflowData = {
- ...workflow,
- id: workflowId,
- dataColumns: [],
- createdAt: Date.now(),
- table: workflow.table || workflow.dataColumns,
- };
- if (Array.isArray(workflowsStorage)) {
- workflowsStorage.push(workflowData);
- } else {
- workflowsStorage[workflowId] = workflowData;
- }
- await browser.storage.local.set({ workflows: workflowsStorage });
- alert('Workflow installed');
- });
|