scordi-extension 1.14.6 → 1.14.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/assets/loading-page-1924caaa.js +51 -0
  2. package/dist/blocks/WaitForConditionBlock.d.ts +176 -0
  3. package/dist/blocks/WaitForConditionBlock.d.ts.map +1 -0
  4. package/dist/blocks/index.d.ts +136 -0
  5. package/dist/blocks/index.d.ts.map +1 -1
  6. package/dist/logo.png +0 -0
  7. package/dist/manifest.json +55 -0
  8. package/dist/public/logo.png +0 -0
  9. package/dist/sdk/index.cjs +1 -1
  10. package/dist/sdk/index.js +3 -2
  11. package/dist/sdk/types.d.ts +1 -1
  12. package/dist/sdk/types.d.ts.map +1 -1
  13. package/dist/service-worker-loader.js +3 -0
  14. package/dist/src/blocks/AiParseDataBlock.ts.js +101 -0
  15. package/dist/src/blocks/ClearValueFormBlock.ts.js +55 -0
  16. package/dist/src/blocks/DataExtractBlock.ts.js +28 -0
  17. package/dist/src/blocks/ElementExistsBlock.ts.js +26 -0
  18. package/dist/src/blocks/EventClickBlock.ts.js +143 -0
  19. package/dist/src/blocks/FetchApiBlock.ts.js +50 -0
  20. package/dist/src/blocks/GetAttributeValueBlock.ts.js +33 -0
  21. package/dist/src/blocks/GetElementDataBlock.ts.js +114 -0
  22. package/dist/src/blocks/GetTextBlock.ts.js +152 -0
  23. package/dist/src/blocks/GetValueFormBlock.ts.js +52 -0
  24. package/dist/src/blocks/KeypressBlock.ts.js +89 -0
  25. package/dist/src/blocks/SaveAssetsBlock.ts.js +35 -0
  26. package/dist/src/blocks/ScrollBlock.ts.js +111 -0
  27. package/dist/src/blocks/SetValueFormBlock.ts.js +56 -0
  28. package/dist/src/blocks/WaitBlock.ts.js +24 -0
  29. package/dist/src/blocks/WaitForConditionBlock.ts.js +187 -0
  30. package/dist/src/blocks/index.ts.js +174 -0
  31. package/dist/src/blocks/types.ts.js +11 -0
  32. package/dist/src/content/components/ConfirmationUI.tsx.js +236 -0
  33. package/dist/src/content/elements/finders/CssSelector.ts.js +51 -0
  34. package/dist/src/content/elements/finders/ElementSelector.ts.js +20 -0
  35. package/dist/src/content/elements/finders/IframeSelector.ts.js +32 -0
  36. package/dist/src/content/elements/finders/ShadowDOMSelector.ts.js +38 -0
  37. package/dist/src/content/elements/finders/XPathFinder.ts.js +32 -0
  38. package/dist/src/content/elements/index.ts.js +26 -0
  39. package/dist/src/content/elements/utils/CSSSelectorGenerator.ts.js +72 -0
  40. package/dist/src/content/elements/utils/XPathGenerator.ts.js +62 -0
  41. package/dist/src/content/handler/ExternalMessageHandler.ts.js +78 -0
  42. package/dist/src/content/handler/InternalMessageHandler.ts.js +18 -0
  43. package/dist/src/content/kernel/MessageKernel.ts.js +83 -0
  44. package/dist/src/content/main.tsx-loader.js +22 -0
  45. package/dist/src/content/main.tsx.js +27 -0
  46. package/dist/src/content/utils/index.ts.js +1 -0
  47. package/dist/src/content/utils/synchronizedLock.ts.js +35 -0
  48. package/dist/src/popup/index.html +12 -0
  49. package/dist/src/types/internal-messages.ts.js +15 -0
  50. package/dist/vendor/.vite-deps-chunk-2TUXWMP5.js__v--9054997c.js +45 -0
  51. package/dist/vendor/.vite-deps-chunk-2TUXWMP5.js__v--e2a1f584.js +45 -0
  52. package/dist/vendor/.vite-deps-chunk-EL3BNLGW.js__v--e2a1f584.js +975 -0
  53. package/dist/vendor/.vite-deps-chunk-QIBDMRD4.js__v--9054997c.js +4158 -0
  54. package/dist/vendor/.vite-deps-chunk-QIBDMRD4.js__v--e2a1f584.js +4158 -0
  55. package/dist/vendor/.vite-deps-chunk-XHY3JSIG.js__v--e2a1f584.js +280 -0
  56. package/dist/vendor/.vite-deps-jsonata.js__v--d602c657.js +5761 -0
  57. package/dist/vendor/.vite-deps-jsonata.js__v--e2a1f584.js +5761 -0
  58. package/dist/vendor/.vite-deps-react-dom.js__v--e2a1f584.js +6 -0
  59. package/dist/vendor/.vite-deps-react-dom_client.js__v--e2a1f584.js +18108 -0
  60. package/dist/vendor/.vite-deps-react.js__v--e2a1f584.js +5 -0
  61. package/dist/vendor/.vite-deps-react_jsx-dev-runtime.js__v--e2a1f584.js +281 -0
  62. package/dist/vendor/.vite-deps-zod.js__v--9e0f4cc1.js +219 -0
  63. package/dist/vendor/.vite-deps-zod.js__v--e2a1f584.js +219 -0
  64. package/dist/vendor/crx-client-port.js +66 -0
  65. package/dist/vendor/crx-client-preamble.js +4 -0
  66. package/dist/vendor/react-refresh.js +670 -0
  67. package/dist/vendor/vite-client.js +1134 -0
  68. package/dist/vendor/vite-dist-client-env.mjs.js +24 -0
  69. package/dist/vendor/webcomponents-custom-elements.js +47 -0
  70. package/package.json +1 -1
@@ -0,0 +1,236 @@
1
+ import { createHotContext as __vite__createHotContext } from "/vendor/vite-client.js";import.meta.hot = __vite__createHotContext("/src/content/components/ConfirmationUI.tsx.js");import __vite__cjsImport0_react_jsxDevRuntime from "/vendor/.vite-deps-react_jsx-dev-runtime.js__v--e2a1f584.js"; const jsxDEV = __vite__cjsImport0_react_jsxDevRuntime["jsxDEV"];
2
+ import * as RefreshRuntime from "/vendor/react-refresh.js";
3
+ const inWebWorker = typeof WorkerGlobalScope !== "undefined" && self instanceof WorkerGlobalScope;
4
+ let prevRefreshReg;
5
+ let prevRefreshSig;
6
+ if (import.meta.hot && !inWebWorker) {
7
+ if (!window.$RefreshReg$) {
8
+ throw new Error(
9
+ "@vitejs/plugin-react can't detect preamble. Something is wrong."
10
+ );
11
+ }
12
+ prevRefreshReg = window.$RefreshReg$;
13
+ prevRefreshSig = window.$RefreshSig$;
14
+ window.$RefreshReg$ = RefreshRuntime.getRefreshReg("/Users/kerry/Documents/GitHub/8g-extension/src/content/components/ConfirmationUI.tsx");
15
+ window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;
16
+ }
17
+ var _s = $RefreshSig$(), _s2 = $RefreshSig$();
18
+ import __vite__cjsImport3_react from "/vendor/.vite-deps-react.js__v--e2a1f584.js"; const useEffect = __vite__cjsImport3_react["useEffect"]; const useState = __vite__cjsImport3_react["useState"];
19
+ import __vite__cjsImport4_reactDom from "/vendor/.vite-deps-react-dom.js__v--e2a1f584.js"; const createPortal = __vite__cjsImport4_reactDom["createPortal"];
20
+ const positionStyles = {
21
+ "top-left": {
22
+ top: "20px",
23
+ left: "20px"
24
+ },
25
+ "top-right": {
26
+ top: "20px",
27
+ right: "20px"
28
+ },
29
+ "bottom-left": {
30
+ bottom: "20px",
31
+ left: "20px"
32
+ },
33
+ "bottom-right": {
34
+ bottom: "20px",
35
+ right: "20px"
36
+ }
37
+ };
38
+ export function ConfirmationUI({
39
+ message,
40
+ buttonText,
41
+ position,
42
+ onConfirm,
43
+ visible
44
+ }) {
45
+ _s();
46
+ const [isAnimating, setIsAnimating] = useState(false);
47
+ useEffect(() => {
48
+ if (visible) {
49
+ setTimeout(() => setIsAnimating(true), 10);
50
+ } else {
51
+ setIsAnimating(false);
52
+ }
53
+ }, [visible]);
54
+ if (!visible) return null;
55
+ const handleConfirm = () => {
56
+ setIsAnimating(false);
57
+ setTimeout(() => {
58
+ onConfirm();
59
+ }, 200);
60
+ };
61
+ const containerStyle = {
62
+ position: "fixed",
63
+ ...positionStyles[position],
64
+ zIndex: 2147483647,
65
+ // Maximum z-index
66
+ backgroundColor: "#ffffff",
67
+ border: "2px solid #4f46e5",
68
+ borderRadius: "12px",
69
+ padding: "16px 20px",
70
+ boxShadow: "0 10px 25px rgba(0, 0, 0, 0.2)",
71
+ display: "flex",
72
+ flexDirection: "column",
73
+ gap: "12px",
74
+ minWidth: "280px",
75
+ maxWidth: "400px",
76
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
77
+ transform: isAnimating ? "scale(1)" : "scale(0.9)",
78
+ opacity: isAnimating ? 1 : 0,
79
+ transition: "transform 0.2s ease-out, opacity 0.2s ease-out"
80
+ };
81
+ const messageStyle = {
82
+ margin: 0,
83
+ fontSize: "14px",
84
+ lineHeight: "1.5",
85
+ color: "#1f2937",
86
+ fontWeight: 500
87
+ };
88
+ const buttonStyle = {
89
+ backgroundColor: "#4f46e5",
90
+ color: "#ffffff",
91
+ border: "none",
92
+ borderRadius: "8px",
93
+ padding: "10px 16px",
94
+ fontSize: "14px",
95
+ fontWeight: 600,
96
+ cursor: "pointer",
97
+ transition: "background-color 0.2s ease",
98
+ outline: "none"
99
+ };
100
+ const buttonHoverStyle = {
101
+ backgroundColor: "#4338ca"
102
+ };
103
+ return createPortal(
104
+ /* @__PURE__ */ jsxDEV(
105
+ "div",
106
+ {
107
+ style: containerStyle,
108
+ onClick: (e) => e.stopPropagation(),
109
+ children: [
110
+ /* @__PURE__ */ jsxDEV("p", { style: messageStyle, children: message }, void 0, false, {
111
+ fileName: "/Users/kerry/Documents/GitHub/8g-extension/src/content/components/ConfirmationUI.tsx",
112
+ lineNumber: 132,
113
+ columnNumber: 7
114
+ }, this),
115
+ /* @__PURE__ */ jsxDEV(
116
+ "button",
117
+ {
118
+ style: buttonStyle,
119
+ onMouseEnter: (e) => {
120
+ e.currentTarget.style.backgroundColor = buttonHoverStyle.backgroundColor;
121
+ },
122
+ onMouseLeave: (e) => {
123
+ e.currentTarget.style.backgroundColor = buttonStyle.backgroundColor;
124
+ },
125
+ onClick: handleConfirm,
126
+ children: buttonText
127
+ },
128
+ void 0,
129
+ false,
130
+ {
131
+ fileName: "/Users/kerry/Documents/GitHub/8g-extension/src/content/components/ConfirmationUI.tsx",
132
+ lineNumber: 133,
133
+ columnNumber: 7
134
+ },
135
+ this
136
+ )
137
+ ]
138
+ },
139
+ void 0,
140
+ true,
141
+ {
142
+ fileName: "/Users/kerry/Documents/GitHub/8g-extension/src/content/components/ConfirmationUI.tsx",
143
+ lineNumber: 128,
144
+ columnNumber: 5
145
+ },
146
+ this
147
+ ),
148
+ document.body
149
+ );
150
+ }
151
+ _s(ConfirmationUI, "U13FD0PO4FR4rREA5Sq0cx8yDCA=");
152
+ _c = ConfirmationUI;
153
+ export function ConfirmationUIContainer() {
154
+ _s2();
155
+ const [uiState, setUiState] = useState({
156
+ visible: false,
157
+ message: "",
158
+ buttonText: "",
159
+ position: "bottom-right",
160
+ onConfirm: null
161
+ });
162
+ useEffect(() => {
163
+ const handleShow = (event) => {
164
+ const customEvent = event;
165
+ const { message, buttonText, position, onConfirm } = customEvent.detail;
166
+ console.log("[ConfirmationUI] Show event received:", { message, buttonText, position });
167
+ setUiState({
168
+ visible: true,
169
+ message,
170
+ buttonText,
171
+ position,
172
+ onConfirm
173
+ });
174
+ };
175
+ const handleHide = () => {
176
+ console.log("[ConfirmationUI] Hide event received");
177
+ setUiState((prev) => ({
178
+ ...prev,
179
+ visible: false
180
+ }));
181
+ };
182
+ window.addEventListener("8g-show-confirmation-ui", handleShow);
183
+ window.addEventListener("8g-hide-confirmation-ui", handleHide);
184
+ return () => {
185
+ window.removeEventListener("8g-show-confirmation-ui", handleShow);
186
+ window.removeEventListener("8g-hide-confirmation-ui", handleHide);
187
+ };
188
+ }, []);
189
+ const handleConfirm = () => {
190
+ console.log("[ConfirmationUI] User confirmed");
191
+ if (uiState.onConfirm) {
192
+ uiState.onConfirm();
193
+ }
194
+ setUiState((prev) => ({
195
+ ...prev,
196
+ visible: false
197
+ }));
198
+ };
199
+ return /* @__PURE__ */ jsxDEV(
200
+ ConfirmationUI,
201
+ {
202
+ message: uiState.message,
203
+ buttonText: uiState.buttonText,
204
+ position: uiState.position,
205
+ onConfirm: handleConfirm,
206
+ visible: uiState.visible
207
+ },
208
+ void 0,
209
+ false,
210
+ {
211
+ fileName: "/Users/kerry/Documents/GitHub/8g-extension/src/content/components/ConfirmationUI.tsx",
212
+ lineNumber: 213,
213
+ columnNumber: 5
214
+ },
215
+ this
216
+ );
217
+ }
218
+ _s2(ConfirmationUIContainer, "UwvSFkkcpfVXM3XMbs+084GMpNI=");
219
+ _c2 = ConfirmationUIContainer;
220
+ var _c, _c2;
221
+ $RefreshReg$(_c, "ConfirmationUI");
222
+ $RefreshReg$(_c2, "ConfirmationUIContainer");
223
+ if (import.meta.hot && !inWebWorker) {
224
+ window.$RefreshReg$ = prevRefreshReg;
225
+ window.$RefreshSig$ = prevRefreshSig;
226
+ }
227
+ if (import.meta.hot && !inWebWorker) {
228
+ RefreshRuntime.__hmr_import(import.meta.url).then((currentExports) => {
229
+ RefreshRuntime.registerExportsForReactRefresh("/Users/kerry/Documents/GitHub/8g-extension/src/content/components/ConfirmationUI.tsx", currentExports);
230
+ import.meta.hot.accept((nextExports) => {
231
+ if (!nextExports) return;
232
+ const invalidateMessage = RefreshRuntime.validateRefreshBoundaryAndEnqueueUpdate("/Users/kerry/Documents/GitHub/8g-extension/src/content/components/ConfirmationUI.tsx", currentExports, nextExports);
233
+ if (invalidateMessage) import.meta.hot.invalidate(invalidateMessage);
234
+ });
235
+ });
236
+ }
@@ -0,0 +1,51 @@
1
+ import { ElementSelector } from "/src/content/elements/finders/ElementSelector.ts.js";
2
+ export class CssSelector extends ElementSelector {
3
+ async find(data, documentCtx = document) {
4
+ const { selector, option } = data;
5
+ const { multiple = false } = option || {};
6
+ try {
7
+ const processedSelector = this.processCustomPseudoSelectors(selector, documentCtx);
8
+ if (multiple) {
9
+ return Array.from(documentCtx.querySelectorAll(processedSelector));
10
+ } else {
11
+ return documentCtx.querySelector(processedSelector);
12
+ }
13
+ } catch (error) {
14
+ console.error("CSS Selector error:", error);
15
+ return null;
16
+ }
17
+ }
18
+ processCustomPseudoSelectors(selector, documentCtx) {
19
+ const containsRegex = /:contains\(['"]([^'"]*)['"]\)/g;
20
+ if (containsRegex.test(selector)) {
21
+ this.markElementsWithText(documentCtx);
22
+ return selector.replace(
23
+ /:contains\(['"]([^'"]*)['"]\)/g,
24
+ (_match, text) => `[data-contains="${text}"]`
25
+ );
26
+ }
27
+ const equalRegex = /:equal\(['"]([^'"]*)['"]\)/g;
28
+ if (equalRegex.test(selector)) {
29
+ this.markElementsWithText(documentCtx);
30
+ return selector.replace(
31
+ /:equal\(['"]([^'"]*)['"]\)/g,
32
+ (_match, text) => `[data-equal="${text}"]`
33
+ );
34
+ }
35
+ return selector;
36
+ }
37
+ markElementsWithText(documentCtx) {
38
+ const allElements = documentCtx.querySelectorAll("*");
39
+ allElements.forEach((element) => {
40
+ const text = element.textContent?.trim() || "";
41
+ if (text) {
42
+ element.setAttribute("data-contains", text);
43
+ const childNodes = Array.from(element.childNodes);
44
+ const directText = childNodes.filter((node) => node.nodeType === Node.TEXT_NODE).map((node) => node.textContent?.trim() || "").join(" ").trim();
45
+ if (directText && directText === text || element.children.length === 0 && text) {
46
+ element.setAttribute("data-equal", text);
47
+ }
48
+ }
49
+ });
50
+ }
51
+ }
@@ -0,0 +1,20 @@
1
+ export class ElementSelector {
2
+ async waitForElement(data, documentCtx, timeout) {
3
+ return new Promise((resolve) => {
4
+ const startTime = Date.now();
5
+ const checkElement = async () => {
6
+ const element = await this.find(data, documentCtx);
7
+ if (element && (Array.isArray(element) ? element.length > 0 : true)) {
8
+ resolve(element);
9
+ return;
10
+ }
11
+ if (Date.now() - startTime >= timeout) {
12
+ resolve(null);
13
+ return;
14
+ }
15
+ setTimeout(checkElement, 100);
16
+ };
17
+ checkElement();
18
+ });
19
+ }
20
+ }
@@ -0,0 +1,32 @@
1
+ import { ElementSelector } from "/src/content/elements/finders/ElementSelector.ts.js";
2
+ export class IframSelector extends ElementSelector {
3
+ async find(data, documentCtx = document) {
4
+ const { selector, option } = data;
5
+ const { multiple = false } = option || {};
6
+ if (!selector || selector.trim() === "") {
7
+ return null;
8
+ }
9
+ const parts = selector.split("|>").map((part) => part.trim());
10
+ if (parts.length !== 2) {
11
+ return null;
12
+ }
13
+ const iframeSelector = parts[0];
14
+ const targetSelector = parts[1];
15
+ if (!iframeSelector || !targetSelector) {
16
+ return null;
17
+ }
18
+ try {
19
+ const iframe = documentCtx.querySelector(iframeSelector);
20
+ if (!iframe || !iframe.contentDocument) return null;
21
+ const iframeDoc = iframe.contentDocument;
22
+ if (multiple) {
23
+ return Array.from(iframeDoc.querySelectorAll(targetSelector));
24
+ } else {
25
+ return iframeDoc.querySelector(targetSelector);
26
+ }
27
+ } catch (error) {
28
+ console.error("Iframe Selector error:", error);
29
+ return null;
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,38 @@
1
+ import { ElementSelector } from "/src/content/elements/finders/ElementSelector.ts.js";
2
+ export class ShadowDOMSelector extends ElementSelector {
3
+ async find(data, documentCtx = document) {
4
+ const { selector, option } = data;
5
+ const { multiple = false } = option || {};
6
+ if (!selector || selector.trim() === "") {
7
+ return null;
8
+ }
9
+ const parts = selector.split(">>").map((part) => part.trim());
10
+ let currentContext = documentCtx;
11
+ try {
12
+ for (let i = 0; i < parts.length; i++) {
13
+ const part = parts[i];
14
+ const isLast = i === parts.length - 1;
15
+ if (!part) {
16
+ return null;
17
+ }
18
+ if (isLast && multiple) {
19
+ return Array.from(currentContext.querySelectorAll(part));
20
+ }
21
+ const element = currentContext.querySelector(part);
22
+ if (!element) return null;
23
+ if (isLast) {
24
+ return element;
25
+ }
26
+ if (element.shadowRoot) {
27
+ currentContext = element.shadowRoot;
28
+ } else {
29
+ return null;
30
+ }
31
+ }
32
+ } catch (error) {
33
+ console.error("Shadow DOM Selector error:", error);
34
+ return null;
35
+ }
36
+ return null;
37
+ }
38
+ }
@@ -0,0 +1,32 @@
1
+ import { ElementSelector } from "/src/content/elements/finders/ElementSelector.ts.js";
2
+ export class XPathSelector extends ElementSelector {
3
+ async find(data, documentCtx = document) {
4
+ const { selector, option } = data;
5
+ const { multiple = false } = option || {};
6
+ try {
7
+ const result = documentCtx.evaluate(
8
+ selector,
9
+ documentCtx,
10
+ null,
11
+ multiple ? XPathResult.ORDERED_NODE_SNAPSHOT_TYPE : XPathResult.FIRST_ORDERED_NODE_TYPE,
12
+ null
13
+ );
14
+ if (multiple) {
15
+ const nodes = [];
16
+ for (let i = 0; i < result.snapshotLength; i++) {
17
+ const node = result.snapshotItem(i);
18
+ if (node && node.nodeType === Node.ELEMENT_NODE) {
19
+ nodes.push(node);
20
+ }
21
+ }
22
+ return Promise.resolve(nodes.length > 0 ? nodes : null);
23
+ } else {
24
+ const node = result.singleNodeValue;
25
+ return node && node.nodeType === Node.ELEMENT_NODE ? node : null;
26
+ }
27
+ } catch (error) {
28
+ console.error("XPath error:", error);
29
+ return null;
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,26 @@
1
+ import { CssSelector } from "/src/content/elements/finders/CssSelector.ts.js";
2
+ import { IframSelector } from "/src/content/elements/finders/IframeSelector.ts.js";
3
+ import { ShadowDOMSelector } from "/src/content/elements/finders/ShadowDOMSelector.ts.js";
4
+ import { XPathSelector } from "/src/content/elements/finders/XPathFinder.ts.js";
5
+ export async function findElement(data, documentCtx = document) {
6
+ const { selector, findBy = "cssSelector", option } = data;
7
+ const { waitForSelector = false, waitSelectorTimeout = 5e3 } = option || {};
8
+ const selectorInstance = buildSelector(selector, findBy);
9
+ if (waitForSelector) {
10
+ return selectorInstance.waitForElement(data, documentCtx, waitSelectorTimeout);
11
+ }
12
+ return selectorInstance.find(data, documentCtx);
13
+ }
14
+ function buildSelector(selector, findBy) {
15
+ if (findBy === "xpath") {
16
+ return new XPathSelector();
17
+ }
18
+ if (selector.includes(">>")) {
19
+ return new ShadowDOMSelector();
20
+ }
21
+ if (selector.includes("|>")) {
22
+ return new IframSelector();
23
+ }
24
+ return new CssSelector();
25
+ }
26
+ export { CSSSelectorGenerator } from "/src/content/elements/utils/CSSSelectorGenerator.ts.js";
@@ -0,0 +1,72 @@
1
+ export class CSSSelectorGenerator {
2
+ /**
3
+ * 요소의 안정적인 CSS 셀렉터 생성
4
+ * @param element 대상 요소
5
+ * @returns 생성된 CSS 셀렉터
6
+ */
7
+ static generate(element) {
8
+ const staticAttributes = ["data-testid", "aria-label", "title", "alt", "role"];
9
+ for (const attr of staticAttributes) {
10
+ const value = element.getAttribute(attr);
11
+ if (value && !this.isDynamicValue(value)) {
12
+ return `[${attr}="${CSS.escape(value)}"]`;
13
+ }
14
+ }
15
+ const tagName = element.tagName.toLowerCase();
16
+ const parent = element.parentElement;
17
+ if (parent) {
18
+ const siblings = Array.from(parent.children);
19
+ const index = siblings.indexOf(element) + 1;
20
+ if (siblings.length > 1) {
21
+ return `${tagName}:nth-child(${index})`;
22
+ }
23
+ }
24
+ if (parent) {
25
+ const parentSelector = this.generateParentSelector(parent);
26
+ if (parentSelector) {
27
+ return `${parentSelector} > ${tagName}:nth-child(${Array.from(parent.children).indexOf(element) + 1})`;
28
+ }
29
+ }
30
+ return tagName;
31
+ }
32
+ /**
33
+ * 부모 요소의 셀렉터 생성
34
+ */
35
+ static generateParentSelector(parent) {
36
+ const tagName = parent.tagName.toLowerCase();
37
+ const staticAttributes = ["data-testid", "aria-label", "title", "alt", "role"];
38
+ for (const attr of staticAttributes) {
39
+ const value = parent.getAttribute(attr);
40
+ if (value && !this.isDynamicValue(value)) {
41
+ return `[${attr}="${CSS.escape(value)}"]`;
42
+ }
43
+ }
44
+ const grandParent = parent.parentElement;
45
+ if (grandParent) {
46
+ const siblings = Array.from(grandParent.children);
47
+ const index = siblings.indexOf(parent) + 1;
48
+ if (siblings.length > 1) {
49
+ return `${tagName}:nth-child(${index})`;
50
+ }
51
+ }
52
+ return tagName;
53
+ }
54
+ /**
55
+ * 동적 값 패턴 감지
56
+ * @param value 확인할 값
57
+ * @returns 동적 값 여부
58
+ */
59
+ static isDynamicValue(value) {
60
+ const dynamicPatterns = [
61
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
62
+ // UUID
63
+ /^\d{13}$/,
64
+ // timestamp
65
+ /^[a-zA-Z0-9]{16,}$/,
66
+ // long random string
67
+ /^[a-zA-Z0-9]+-\d+$/
68
+ // name-123 pattern
69
+ ];
70
+ return dynamicPatterns.some((pattern) => pattern.test(value));
71
+ }
72
+ }
@@ -0,0 +1,62 @@
1
+ export class XPathGenerator {
2
+ /**
3
+ * 요소의 XPath 생성
4
+ * @param element 대상 요소
5
+ * @returns 생성된 XPath
6
+ */
7
+ static generate(element) {
8
+ const path = [];
9
+ let current = element;
10
+ while (current && current.nodeType === Node.ELEMENT_NODE) {
11
+ let selector = current.tagName.toLowerCase();
12
+ const siblings = Array.from(current.parentElement?.children || []).filter((sibling) => sibling.tagName === current.tagName);
13
+ if (siblings.length > 1) {
14
+ const index = siblings.indexOf(current) + 1;
15
+ selector += `[${index}]`;
16
+ }
17
+ const attributes = this.getStableAttributes(current);
18
+ if (attributes.length > 0) {
19
+ selector += `[@${attributes.join(" and @")}]`;
20
+ }
21
+ path.unshift(selector);
22
+ current = current.parentElement;
23
+ }
24
+ return "/" + path.join("/");
25
+ }
26
+ /**
27
+ * 요소의 안정적인 속성들을 반환
28
+ * @param element 대상 요소
29
+ * @returns 안정적인 속성 배열
30
+ */
31
+ static getStableAttributes(element) {
32
+ const stableAttributes = [];
33
+ const stableAttrs = ["data-testid", "aria-label", "title", "alt", "role"];
34
+ for (const attr of stableAttrs) {
35
+ const value = element.getAttribute(attr);
36
+ if (value && !this.isDynamicValue(value)) {
37
+ stableAttributes.push(`${attr}="${value}"`);
38
+ }
39
+ }
40
+ return stableAttributes;
41
+ }
42
+ /**
43
+ * 동적 값 패턴 감지
44
+ * @param value 확인할 값
45
+ * @returns 동적 값 여부
46
+ */
47
+ static isDynamicValue(value) {
48
+ const dynamicPatterns = [
49
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
50
+ // UUID
51
+ /^\d{13}$/,
52
+ // timestamp
53
+ /^[a-zA-Z0-9]{16,}$/,
54
+ // long random string
55
+ /^[a-zA-Z0-9]+-\d+$/,
56
+ // name-123 pattern
57
+ /^[a-zA-Z0-9]+_[a-zA-Z0-9]+$/
58
+ // underscore pattern
59
+ ];
60
+ return dynamicPatterns.some((pattern) => pattern.test(value));
61
+ }
62
+ }
@@ -0,0 +1,78 @@
1
+ export class ExternalMessageHandler {
2
+ constructor(kernel) {
3
+ this.kernel = kernel;
4
+ }
5
+ /**
6
+ * 웹페이지로부터의 window message 리스너 초기화
7
+ */
8
+ initializeMessageListener() {
9
+ window.addEventListener("message", this.handleWindowMessage.bind(this));
10
+ }
11
+ /**
12
+ * 웹페이지로부터의 window message 처리
13
+ */
14
+ async handleWindowMessage(event) {
15
+ if (event.source !== window) return;
16
+ const message = event.data;
17
+ switch (message.type) {
18
+ case "8G_EXTENSION_CHECK":
19
+ this.handleExtensionCheck();
20
+ break;
21
+ case "8G_COLLECT_WORKFLOW":
22
+ await this.handleCollectWorkflow(message);
23
+ break;
24
+ default:
25
+ break;
26
+ }
27
+ }
28
+ /**
29
+ * 확장 프로그램 설치 확인 요청 처리
30
+ */
31
+ handleExtensionCheck() {
32
+ const response = {
33
+ type: "8G_EXTENSION_RESPONSE",
34
+ installed: true,
35
+ version: "1.0.0"
36
+ };
37
+ this.kernel.sendToWebpage(response);
38
+ }
39
+ /**
40
+ * 워크플로우 수집 요청 처리
41
+ */
42
+ async handleCollectWorkflow(message) {
43
+ try {
44
+ this.validateCollectWorkflowMessage(message);
45
+ const backgroundMessage = {
46
+ type: "COLLECT_WORKFLOW_NEW_TAB",
47
+ data: {
48
+ targetUrl: message.targetUrl,
49
+ workflow: message.workflow,
50
+ closeTabAfterCollection: message.closeTabAfterCollection !== false,
51
+ activateTab: message.activateTab === true
52
+ }
53
+ };
54
+ try {
55
+ const response = await this.kernel.sendToBackground(backgroundMessage);
56
+ const successResponse = this.kernel.createSuccessResponse(message.requestId, response);
57
+ this.kernel.sendToWebpage(successResponse);
58
+ } catch (error) {
59
+ const errorResponse = this.kernel.createErrorResponse(message.requestId, error);
60
+ this.kernel.sendToWebpage(errorResponse);
61
+ }
62
+ } catch (error) {
63
+ const errorResponse = this.kernel.createErrorResponse(message.requestId, error);
64
+ this.kernel.sendToWebpage(errorResponse);
65
+ }
66
+ }
67
+ validateCollectWorkflowMessage(message) {
68
+ if (!message.targetUrl) {
69
+ throw new Error("Target URL is required");
70
+ }
71
+ if (!message.workflow) {
72
+ throw new Error("Workflow is required");
73
+ }
74
+ if (!message.workflow.start || !Array.isArray(message.workflow.steps)) {
75
+ throw new Error("Workflow must have start and steps");
76
+ }
77
+ }
78
+ }
@@ -0,0 +1,18 @@
1
+ import { isExecuteBlockMessage } from "/src/types/internal-messages.ts.js";
2
+ export class InternalMessageHandler {
3
+ constructor(kernel) {
4
+ this.kernel = kernel;
5
+ }
6
+ /**
7
+ * Background script로부터의 메시지 리스너 초기화
8
+ */
9
+ initializeMessageListener() {
10
+ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
11
+ if (isExecuteBlockMessage(message)) {
12
+ this.kernel.handleRuntimeMessage(message).then((result) => sendResponse(result)).catch((error) => sendResponse(this.kernel.createErrorResponse("", error)));
13
+ return true;
14
+ }
15
+ return false;
16
+ });
17
+ }
18
+ }