handlerJavascriptCode.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. import { customAlphabet } from 'nanoid/non-secure';
  2. import browser from 'webextension-polyfill';
  3. import cloneDeep from 'lodash.clonedeep';
  4. import { jsContentHandler } from '@/newtab/utils/javascriptBlockUtil';
  5. import { messageSandbox, automaRefDataStr, waitTabLoaded } from '../helper';
  6. const nanoid = customAlphabet('1234567890abcdef', 5);
  7. function getAutomaScript(refData, everyNewTab) {
  8. const varName = `automa${nanoid()}`;
  9. let str = `
  10. const ${varName} = ${JSON.stringify(refData)};
  11. ${automaRefDataStr(varName)}
  12. function automaSetVariable(name, value) {
  13. ${varName}.variables[name] = value;
  14. }
  15. function automaNextBlock(data, insert = true) {
  16. document.body.dispatchEvent(new CustomEvent('__automa-next-block__', { detail: { data, insert, refData: ${varName} } }));
  17. }
  18. function automaResetTimeout() {
  19. document.body.dispatchEvent(new CustomEvent('__automa-reset-timeout__'));
  20. }
  21. `;
  22. if (everyNewTab) str = automaRefDataStr(varName);
  23. return str;
  24. }
  25. async function executeInWebpage(args, target, worker) {
  26. if (worker.engine.isMV2 || BROWSER_TYPE === 'firefox') {
  27. args[0] = cloneDeep(args[0]);
  28. const result = await worker._sendMessageToTab({
  29. label: 'javascript-code',
  30. data: args,
  31. });
  32. return result;
  33. }
  34. const [{ result }] = await browser.scripting.executeScript({
  35. args,
  36. target,
  37. world: 'MAIN',
  38. func: jsContentHandler,
  39. });
  40. return result;
  41. }
  42. export async function javascriptCode({ outputs, data, ...block }, { refData }) {
  43. const nextBlockId = this.getBlockConnections(block.id);
  44. if (data.everyNewTab) {
  45. const isScriptExist = this.preloadScripts.some(({ id }) => id === block.id);
  46. if (!isScriptExist)
  47. this.preloadScripts.push({ id: block.id, data: cloneDeep(data) });
  48. if (!this.activeTab.id) return { data: '', nextBlockId };
  49. } else if (!this.activeTab.id && data.context !== 'background') {
  50. throw new Error('no-tab');
  51. }
  52. const payload = {
  53. ...block,
  54. data,
  55. refData: { variables: {} },
  56. frameSelector: this.frameSelector,
  57. };
  58. if (data.code.includes('automaRefData')) {
  59. payload.refData = { ...refData, secrets: {} };
  60. }
  61. const preloadScriptsPromise = await Promise.allSettled(
  62. data.preloadScripts.map(async (script) => {
  63. const { protocol } = new URL(script.src);
  64. const isValidUrl = /https?/.test(protocol);
  65. if (!isValidUrl) return null;
  66. const response = await fetch(script.src);
  67. if (!response.ok) throw new Error(response.statusText);
  68. const result = await response.text();
  69. return {
  70. script: result,
  71. id: `automa-script-${nanoid()}`,
  72. removeAfterExec: script.removeAfterExec,
  73. };
  74. })
  75. );
  76. const preloadScripts = preloadScriptsPromise.reduce((acc, item) => {
  77. if (item.status === 'fulfilled') acc.push(item.value);
  78. return acc;
  79. }, []);
  80. const automaScript =
  81. data.everyNewTab || data.context === 'background'
  82. ? ''
  83. : getAutomaScript(payload.refData, data.everyNewTab);
  84. if (data.context !== 'background') {
  85. await waitTabLoaded({
  86. tabId: this.activeTab.id,
  87. ms: this.settings?.tabLoadTimeout ?? 30000,
  88. });
  89. }
  90. const result = await (data.context === 'background'
  91. ? messageSandbox('javascriptBlock', {
  92. preloadScripts,
  93. refData: payload.refData,
  94. blockData: cloneDeep(payload.data),
  95. })
  96. : executeInWebpage(
  97. [payload, preloadScripts, automaScript],
  98. {
  99. tabId: this.activeTab.id,
  100. frameIds: [this.activeTab.frameId || 0],
  101. },
  102. this
  103. ));
  104. if (result) {
  105. if (result.columns.data?.$error) {
  106. throw new Error(result.columns.data.message);
  107. }
  108. if (result.variables) {
  109. Object.keys(result.variables).forEach((varName) => {
  110. this.setVariable(varName, result.variables[varName]);
  111. });
  112. }
  113. if (result.columns.insert && result.columns.data) {
  114. const params = Array.isArray(result.columns.data)
  115. ? result.columns.data
  116. : [result.columns.data];
  117. this.addDataToColumn(params);
  118. }
  119. }
  120. return {
  121. nextBlockId,
  122. data: result?.columns.data || {},
  123. };
  124. }
  125. export default javascriptCode;