arcy.js 0.1.0 → 0.1.2

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.
@@ -0,0 +1,213 @@
1
+ /* arcy.js — https://arcyai.com */
2
+
3
+ // src/fingerprint/model.ts
4
+ var FINGERPRINT_TEXT_MAX = 80;
5
+ var SELECTOR_CANDIDATES_MAX = 5;
6
+ var PRECISION_DEFAULT = 3;
7
+ var ORDINAL_DEFAULT = 1;
8
+ function createTargetFingerprint(core) {
9
+ return {
10
+ v: 1,
11
+ core,
12
+ selector: null,
13
+ selectorCandidates: [],
14
+ ordinal: ORDINAL_DEFAULT,
15
+ precision: PRECISION_DEFAULT,
16
+ dynamicText: false
17
+ };
18
+ }
19
+
20
+ // src/fingerprint/shadow.ts
21
+ function parentOrHost(el) {
22
+ if (el.parentElement !== null) return el.parentElement;
23
+ const parentNode = el.parentNode;
24
+ if (parentNode !== null && "host" in parentNode) {
25
+ return parentNode.host;
26
+ }
27
+ return null;
28
+ }
29
+ function closestAcrossShadow(el, selector) {
30
+ let current = el;
31
+ while (current !== null) {
32
+ if (current.matches(selector)) return current;
33
+ current = parentOrHost(current);
34
+ }
35
+ return null;
36
+ }
37
+ function containsAcrossShadow(ancestor, descendant) {
38
+ let current = descendant;
39
+ while (current !== null) {
40
+ if (current === ancestor) return true;
41
+ current = parentOrHost(current);
42
+ }
43
+ return false;
44
+ }
45
+ var MAX_SHADOW_DEPTH = 100;
46
+ function collectElementsDeep(root, depth = 0) {
47
+ const out = [];
48
+ const all = root.querySelectorAll("*");
49
+ for (let i = 0; i < all.length; i += 1) {
50
+ const el = all[i];
51
+ out.push(el);
52
+ if (el.shadowRoot !== null && depth < MAX_SHADOW_DEPTH)
53
+ out.push(...collectElementsDeep(el.shadowRoot, depth + 1));
54
+ }
55
+ return out;
56
+ }
57
+ function querySelectorAllDeep(root, selector, depth = 0) {
58
+ const out = Array.from(root.querySelectorAll(selector));
59
+ const all = root.querySelectorAll("*");
60
+ for (let i = 0; i < all.length; i += 1) {
61
+ const el = all[i];
62
+ if (el.shadowRoot !== null && depth < MAX_SHADOW_DEPTH)
63
+ out.push(...querySelectorAllDeep(el.shadowRoot, selector, depth + 1));
64
+ }
65
+ return out;
66
+ }
67
+
68
+ // src/fingerprint/capture.ts
69
+ var PII_PATTERNS = [
70
+ // Email
71
+ /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
72
+ // US SSN: 123-45-6789
73
+ /\b\d{3}-\d{2}-\d{4}\b/g,
74
+ // Card shapes: 13-19 digits, optionally grouped. A denylist, not a Luhn check.
75
+ /\b\d(?:[ -]?\d){12,18}\b/g
76
+ ];
77
+ var MASK = "[masked]";
78
+ function containsPii(raw) {
79
+ return PII_PATTERNS.some((pattern) => {
80
+ pattern.lastIndex = 0;
81
+ return pattern.test(raw);
82
+ });
83
+ }
84
+ var NORMALIZE_RAW_MAX = 4096;
85
+ var NORMALIZE_RAW_MARGIN = 100;
86
+ var ROLE_MAX = 64;
87
+ function normalizeText(raw) {
88
+ if (!raw) return void 0;
89
+ let bounded = raw.slice(0, NORMALIZE_RAW_MAX);
90
+ if (raw.length > NORMALIZE_RAW_MAX) {
91
+ const lastSpace = bounded.search(/\s\S*$/);
92
+ bounded = lastSpace === -1 ? bounded.slice(0, NORMALIZE_RAW_MAX - NORMALIZE_RAW_MARGIN) : bounded.slice(0, lastSpace);
93
+ }
94
+ let text = bounded.replace(/\s+/g, " ").trim();
95
+ if (!text) return void 0;
96
+ for (const pattern of PII_PATTERNS) {
97
+ text = text.replace(pattern, MASK);
98
+ }
99
+ return text.slice(0, FINGERPRINT_TEXT_MAX);
100
+ }
101
+ function isStableId(id) {
102
+ if (id.length === 0 || id.length > 64) return false;
103
+ if (id.includes(":")) return false;
104
+ if (id.startsWith("radix-")) return false;
105
+ if (/^\d+$/.test(id)) return false;
106
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id)) return false;
107
+ return true;
108
+ }
109
+ var INTERACTIVE_TAGS = /* @__PURE__ */ new Set(["button", "input", "select", "textarea", "label", "summary"]);
110
+ var INTERACTIVE_ROLES = /* @__PURE__ */ new Set(["button", "link", "menuitem", "tab", "option"]);
111
+ function isInteractive(el) {
112
+ const tag = el.tagName.toLowerCase();
113
+ if (INTERACTIVE_TAGS.has(tag)) return true;
114
+ if (tag === "a" && el.hasAttribute("href")) return true;
115
+ const role = el.getAttribute("role");
116
+ if (role !== null && INTERACTIVE_ROLES.has(role)) return true;
117
+ const editable = el.getAttribute("contenteditable");
118
+ if (editable === "" || editable === "true") return true;
119
+ return false;
120
+ }
121
+ function resolveInteractiveTarget(el) {
122
+ let current = el;
123
+ while (current !== null && current !== current.ownerDocument.documentElement) {
124
+ if (isInteractive(current)) return current;
125
+ current = parentOrHost(current);
126
+ }
127
+ return el;
128
+ }
129
+ var ANCESTOR_ATTRS = ["data-testid", "data-test", "data-cy", "id", "name", "aria-label"];
130
+ var TEST_ATTRS = ["data-testid", "data-test", "data-cy"];
131
+ var TEST_ID_MAX = 64;
132
+ function firstTestId(el) {
133
+ for (const attr of TEST_ATTRS) {
134
+ const raw = el.getAttribute(attr);
135
+ if (raw === null) continue;
136
+ const trimmed = raw.trim();
137
+ if (trimmed === "" || trimmed.length > TEST_ID_MAX) continue;
138
+ if (containsPii(trimmed)) continue;
139
+ return trimmed;
140
+ }
141
+ return void 0;
142
+ }
143
+ var LANDMARK_ROLES = /* @__PURE__ */ new Set([
144
+ "navigation",
145
+ "main",
146
+ "banner",
147
+ "contentinfo",
148
+ "search",
149
+ "form",
150
+ "region",
151
+ "dialog"
152
+ ]);
153
+ function findSemanticAncestor(el) {
154
+ let current = parentOrHost(el);
155
+ while (current !== null) {
156
+ const tag = current.tagName.toLowerCase();
157
+ if (tag === "body" || tag === "html") return void 0;
158
+ for (const attr of ANCESTOR_ATTRS) {
159
+ const raw = current.getAttribute(attr);
160
+ if (raw === null) continue;
161
+ if (attr === "id" && !isStableId(raw)) continue;
162
+ const value = normalizeText(raw);
163
+ if (value !== void 0) return { tag, attr, value };
164
+ }
165
+ const role = current.getAttribute("role");
166
+ if (role !== null && LANDMARK_ROLES.has(role)) {
167
+ return { tag, attr: "role", value: role };
168
+ }
169
+ current = parentOrHost(current);
170
+ }
171
+ return void 0;
172
+ }
173
+ function siblingIndexOf(el) {
174
+ let index = 0;
175
+ let sibling = el.previousElementSibling;
176
+ while (sibling !== null) {
177
+ if (sibling.tagName === el.tagName) index += 1;
178
+ sibling = sibling.previousElementSibling;
179
+ }
180
+ return index;
181
+ }
182
+ function captureEventFingerprint(el) {
183
+ const fingerprint = {
184
+ tag: el.tagName.toLowerCase(),
185
+ siblingIndex: siblingIndexOf(el)
186
+ };
187
+ const text = normalizeText(el.textContent);
188
+ if (text !== void 0) fingerprint.text = text;
189
+ const ariaLabel = normalizeText(el.getAttribute("aria-label"));
190
+ if (ariaLabel !== void 0) fingerprint.ariaLabel = ariaLabel;
191
+ const placeholder = normalizeText(el.getAttribute("placeholder"));
192
+ if (placeholder !== void 0) fingerprint.placeholder = placeholder;
193
+ const id = el.getAttribute("id");
194
+ if (id !== null && isStableId(id) && !containsPii(id)) fingerprint.id = id;
195
+ const role = el.getAttribute("role");
196
+ if (role !== null) {
197
+ const trimmed = role.trim();
198
+ if (trimmed !== "" && trimmed.length <= ROLE_MAX && !containsPii(trimmed)) {
199
+ fingerprint.role = trimmed;
200
+ }
201
+ }
202
+ const testId = firstTestId(el);
203
+ if (testId !== void 0) fingerprint.testId = testId;
204
+ const ancestor = findSemanticAncestor(el);
205
+ if (ancestor !== void 0) {
206
+ fingerprint.ancestorTag = ancestor.tag;
207
+ fingerprint.ancestorAttr = ancestor.attr;
208
+ fingerprint.ancestorValue = ancestor.value;
209
+ }
210
+ return fingerprint;
211
+ }
212
+
213
+ export { FINGERPRINT_TEXT_MAX, MASK, SELECTOR_CANDIDATES_MAX, captureEventFingerprint, closestAcrossShadow, collectElementsDeep, containsAcrossShadow, containsPii, createTargetFingerprint, isInteractive, isStableId, querySelectorAllDeep, resolveInteractiveTarget };
@@ -1,127 +1,2 @@
1
- /* arcy.js https://arcyai.com */
2
-
3
- // src/fingerprint/model.ts
4
- var FINGERPRINT_TEXT_MAX = 80;
5
- var SELECTOR_CANDIDATES_MAX = 5;
6
-
7
- // src/fingerprint/capture.ts
8
- var MASK = "[masked]";
9
-
10
- // src/picker/message.ts
11
- var DESIGN_MODE_PROTOCOL = 2;
12
- var DESIGN_MODE_READY = "arcy:design-mode:ready";
13
- var DESIGN_MODE_PICK = "arcy:design-mode:pick";
14
- var DESIGN_MODE_EXIT = "arcy:design-mode:exit";
15
- var DESIGN_MODE_SCREENSHOT = "arcy:design-mode:screenshot";
16
- var TAG_MAX = 64;
17
- var ROUTE_MAX = 2048;
18
- var SELECTOR_MAX = 512;
19
- var ORDINAL_MAX = 1e4;
20
- var SIBLING_INDEX_MAX = 1e5;
21
- var SCREENSHOT_DATA_URL_MAX = 4e6;
22
- var SCREENSHOT_PREFIX = "data:image/png;base64,";
23
- var MATCH_COUNT_MAX = 1e6;
24
- function isRecord(value) {
25
- return typeof value === "object" && value !== null && !Array.isArray(value);
26
- }
27
- function isBoundedString(value, max) {
28
- return typeof value === "string" && value.length > 0 && value.length <= max;
29
- }
30
- function isOptionalBoundedString(value, max) {
31
- return value === void 0 || isBoundedString(value, max);
32
- }
33
- function isBoundedInteger(value, min, max) {
34
- return typeof value === "number" && Number.isInteger(value) && value >= min && value <= max;
35
- }
36
- function readCore(value) {
37
- if (!isRecord(value)) return null;
38
- if (!isBoundedString(value.tag, TAG_MAX)) return null;
39
- if (!isBoundedInteger(value.siblingIndex, 0, SIBLING_INDEX_MAX)) return null;
40
- const optional = [
41
- "text",
42
- "ariaLabel",
43
- "placeholder",
44
- "id",
45
- "role",
46
- "testId",
47
- "ancestorTag",
48
- "ancestorAttr",
49
- "ancestorValue"
50
- ];
51
- for (const key of optional) {
52
- if (!isOptionalBoundedString(value[key], FINGERPRINT_TEXT_MAX)) return null;
53
- }
54
- const core = {
55
- tag: value.tag,
56
- siblingIndex: value.siblingIndex
57
- };
58
- for (const key of optional) {
59
- const field = value[key];
60
- if (typeof field === "string") core[key] = field;
61
- }
62
- return core;
63
- }
64
- function readTarget(value) {
65
- if (!isRecord(value)) return null;
66
- if (value.v !== 1) return null;
67
- const core = readCore(value.core);
68
- if (!core) return null;
69
- const { selector, selectorCandidates, ordinal, precision, dynamicText } = value;
70
- if (selector !== null && !isBoundedString(selector, SELECTOR_MAX)) return null;
71
- if (!Array.isArray(selectorCandidates)) return null;
72
- if (selectorCandidates.length > SELECTOR_CANDIDATES_MAX) return null;
73
- if (!selectorCandidates.every((c) => isBoundedString(c, SELECTOR_MAX)))
74
- return null;
75
- if (!isBoundedInteger(ordinal, 1, ORDINAL_MAX)) return null;
76
- if (!isBoundedInteger(precision, 1, 5)) return null;
77
- if (typeof dynamicText !== "boolean") return null;
78
- return {
79
- v: 1,
80
- core,
81
- selector,
82
- selectorCandidates,
83
- ordinal,
84
- precision,
85
- dynamicText
86
- };
87
- }
88
- function readDesignModeMessage(data) {
89
- if (!isRecord(data)) return null;
90
- if (data.protocol !== DESIGN_MODE_PROTOCOL) return null;
91
- switch (data.type) {
92
- case DESIGN_MODE_READY:
93
- return { type: DESIGN_MODE_READY, protocol: DESIGN_MODE_PROTOCOL };
94
- case DESIGN_MODE_EXIT:
95
- return { type: DESIGN_MODE_EXIT, protocol: DESIGN_MODE_PROTOCOL };
96
- case DESIGN_MODE_PICK: {
97
- const target = readTarget(data.target);
98
- if (!target) return null;
99
- const { route, matchCount } = data;
100
- if (!isBoundedString(route, ROUTE_MAX)) return null;
101
- if (!route.startsWith("/")) return null;
102
- if (route.startsWith("//")) return null;
103
- if (!isBoundedInteger(matchCount, 0, MATCH_COUNT_MAX)) return null;
104
- return {
105
- type: DESIGN_MODE_PICK,
106
- protocol: DESIGN_MODE_PROTOCOL,
107
- target,
108
- route,
109
- matchCount
110
- };
111
- }
112
- case DESIGN_MODE_SCREENSHOT: {
113
- const { screenshot } = data;
114
- if (!isBoundedString(screenshot, SCREENSHOT_DATA_URL_MAX)) return null;
115
- if (!screenshot.startsWith(SCREENSHOT_PREFIX)) return null;
116
- return {
117
- type: DESIGN_MODE_SCREENSHOT,
118
- protocol: DESIGN_MODE_PROTOCOL,
119
- screenshot
120
- };
121
- }
122
- default:
123
- return null;
124
- }
125
- }
126
-
127
- export { DESIGN_MODE_EXIT, DESIGN_MODE_PICK, DESIGN_MODE_PROTOCOL, DESIGN_MODE_READY, DESIGN_MODE_SCREENSHOT, MASK, readDesignModeMessage };
1
+ export { DESIGN_MODE_EXIT, DESIGN_MODE_PICK, DESIGN_MODE_PROTOCOL, DESIGN_MODE_READY, DESIGN_MODE_SCREENSHOT, readDesignModeMessage } from './chunk-7VVKR3AO.js';
2
+ export { MASK } from './chunk-NY3NXM2V.js';