chocolatito-code 1.0.0 → 1.2.0
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.
- package/README.md +155 -0
- package/dist/agent/compaction.d.ts +65 -0
- package/dist/agent/compaction.js +134 -0
- package/dist/agent/context.d.ts +3 -0
- package/dist/agent/context.js +35 -0
- package/dist/agent/loop.d.ts +91 -2
- package/dist/agent/loop.js +642 -91
- package/dist/agent/loopDetector.d.ts +57 -0
- package/dist/agent/loopDetector.js +0 -0
- package/dist/agent/mentions.d.ts +13 -0
- package/dist/agent/mentions.js +49 -0
- package/dist/agent/repoMap.d.ts +117 -0
- package/dist/agent/repoMap.js +560 -0
- package/dist/agent/result.d.ts +64 -0
- package/dist/agent/result.js +48 -0
- package/dist/agent/retry.d.ts +35 -0
- package/dist/agent/retry.js +123 -4
- package/dist/agent/salidaDelAgente.d.ts +43 -0
- package/dist/agent/salidaDelAgente.js +53 -0
- package/dist/agent/subagent.js +62 -15
- package/dist/agent/syntaxValidator.js +288 -52
- package/dist/agent/toolGate.d.ts +58 -0
- package/dist/agent/toolGate.js +111 -0
- package/dist/agent/tracker.js +4 -7
- package/dist/agent/undoManager.d.ts +44 -11
- package/dist/agent/undoManager.js +95 -24
- package/dist/agent/usageMeter.d.ts +56 -0
- package/dist/agent/usageMeter.js +51 -0
- package/dist/agent/verifier.d.ts +49 -0
- package/dist/agent/verifier.js +158 -0
- package/dist/config/constants.js +14 -0
- package/dist/config/engine.d.ts +19 -0
- package/dist/config/engine.js +35 -3
- package/dist/config/license.d.ts +5 -0
- package/dist/config/license.js +107 -9
- package/dist/config/limits.d.ts +28 -0
- package/dist/config/limits.js +51 -0
- package/dist/config/nodeVersion.d.ts +13 -0
- package/dist/config/nodeVersion.js +26 -0
- package/dist/config/permissions.d.ts +76 -1
- package/dist/config/permissions.js +379 -23
- package/dist/config/quota.js +24 -1
- package/dist/config/updater.d.ts +68 -0
- package/dist/config/updater.js +215 -0
- package/dist/hooks/manager.js +159 -14
- package/dist/index.js +240 -37
- package/dist/mcp/client.d.ts +50 -0
- package/dist/mcp/client.js +155 -19
- package/dist/mcp/ide.d.ts +76 -0
- package/dist/mcp/ide.js +160 -0
- package/dist/mcp/manager.js +9 -0
- package/dist/prompts/systemPrompt.js +12 -1
- package/dist/sessions/manager.d.ts +12 -0
- package/dist/sessions/manager.js +33 -5
- package/dist/sessions/resume.d.ts +36 -0
- package/dist/sessions/resume.js +43 -0
- package/dist/skills/manager.d.ts +1 -0
- package/dist/skills/manager.js +9 -0
- package/dist/tools/binary.d.ts +46 -0
- package/dist/tools/binary.js +100 -0
- package/dist/tools/browserCdp.js +13 -1
- package/dist/tools/browserExtension.d.ts +5 -0
- package/dist/tools/browserExtension.js +237 -22
- package/dist/tools/browserSession.d.ts +23 -0
- package/dist/tools/browserSession.js +28 -0
- package/dist/tools/computerUse.js +33 -1
- package/dist/tools/createImage.d.ts +11 -0
- package/dist/tools/createImage.js +13 -1
- package/dist/tools/definitions.js +17 -4
- package/dist/tools/editDiagnosis.d.ts +54 -0
- package/dist/tools/editDiagnosis.js +142 -0
- package/dist/tools/editFile.d.ts +1 -0
- package/dist/tools/editFile.js +118 -62
- package/dist/tools/findFiles.d.ts +21 -0
- package/dist/tools/findFiles.js +75 -6
- package/dist/tools/getSystemInfo.js +15 -7
- package/dist/tools/gitTools.d.ts +24 -0
- package/dist/tools/gitTools.js +87 -26
- package/dist/tools/grepSearch.js +111 -23
- package/dist/tools/moveCopyFile.d.ts +18 -1
- package/dist/tools/moveCopyFile.js +74 -4
- package/dist/tools/patchCascade.d.ts +57 -0
- package/dist/tools/patchCascade.js +338 -0
- package/dist/tools/runCommand.js +213 -25
- package/dist/tools/runner.js +58 -5
- package/dist/tools/toolDefsComputer.js +12 -2
- package/dist/tools/truncator.d.ts +27 -1
- package/dist/tools/truncator.js +117 -11
- package/dist/tools/viewFile.js +15 -11
- package/dist/tools/visionBridge.js +7 -0
- package/dist/tools/webSearch.d.ts +21 -0
- package/dist/tools/webSearch.js +125 -14
- package/dist/tools/writeFile.d.ts +16 -1
- package/dist/tools/writeFile.js +40 -2
- package/dist/ui/interrupt.d.ts +4 -1
- package/dist/ui/interrupt.js +59 -7
- package/dist/ui/keyboardGuard.d.ts +76 -0
- package/dist/ui/keyboardGuard.js +94 -0
- package/dist/ui/loopPrompt.d.ts +17 -0
- package/dist/ui/loopPrompt.js +59 -0
- package/dist/ui/pegados.d.ts +50 -0
- package/dist/ui/pegados.js +83 -0
- package/dist/ui/permissionPrompt.js +84 -2
- package/dist/ui/prompt.d.ts +40 -0
- package/dist/ui/prompt.js +436 -131
- package/dist/ui/reasoningStream.d.ts +8 -0
- package/dist/ui/reasoningStream.js +99 -7
- package/dist/ui/salida.d.ts +10 -0
- package/dist/ui/salida.js +50 -0
- package/extension/README.md +95 -74
- package/extension/background.js +772 -181
- package/extension/content.js +527 -46
- package/extension/fuentes/Chocolatito-Marca.ttf +0 -0
- package/extension/iconos/128.png +0 -0
- package/extension/iconos/16.png +0 -0
- package/extension/iconos/32.png +0 -0
- package/extension/iconos/48.png +0 -0
- package/extension/iconos/logotipo.png +0 -0
- package/extension/manifest.json +28 -4
- package/extension/popup.html +16 -6
- package/extension/popup.js +28 -12
- package/package.json +3 -2
package/extension/content.js
CHANGED
|
@@ -17,7 +17,245 @@
|
|
|
17
17
|
|
|
18
18
|
const IS_TOP = window.top === window;
|
|
19
19
|
|
|
20
|
-
// ========================================================
|
|
20
|
+
// ======================================================== overlay visual y borde naranja (#D97757)
|
|
21
|
+
|
|
22
|
+
let overlayHost = null;
|
|
23
|
+
let overlayShadow = null;
|
|
24
|
+
let overlayFrame = null;
|
|
25
|
+
let overlayLayers = null;
|
|
26
|
+
let overlayActivo = false;
|
|
27
|
+
|
|
28
|
+
// Icono oficial del zorro de Chocolatito a 16px en base64 para cargar de inmediato
|
|
29
|
+
// sin depender de peticiones de red ni fallar por políticas CSP de páginas externas.
|
|
30
|
+
const LOGO_16_DATA_URI =
|
|
31
|
+
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC10lEQVR4nI1RS2hUZxT+zv+4uZk7c5MxNk5SiI/4IhZUhIJ2IVnYRbGQUqQU3YhS64OgGxdubDcqWIraldVaSqmIYLGL4mPhA6QUtFIxLlQ0iQnJSHQyjncmM/fe/z/lDplixBY/OItzOOc73/kO8AoYoCSGNnf1DGzs6klqV9dC4X8gZmQ/wCGA2zP4YFlOXA92Lfqo9zpi3g35VgS0BbWez/dKN4o/iyrhbI+qv5e+nLeVjsD8vfPNJNSQ/vTAh2rOyN3VAB8MQn5PKe1PxYysI2J4/kr65t4An4egPtgZCniaxAkm3HIo9hVqojIZ0plSaK5VIi5DsAJP7a5369dObiho4OrXy5rcMe/d1mq+1wWnx628tEBUL85pc0P6dmhxsizx6I0e8A7I3v33aqNDw/mcinbOy9KRdnA1oKZ9qKKE/4CqD5+DoE9hCtuWLM+qqdNBzfQgtFYL/mSc5Y9zY1rKv0ChCMkjiOkQzL8EZzdAJsPV7d1rmmRwIYzIL4bxVFprx1ecLabn83Bx8tjCjYiBeuDVUyhJHm1+X3fqJ4PNSnaOldm0OCS9rEahEG9pOzl6ivs7V8PqNXBlBqT/pMP3LzZIVMIy1JFmb5xvWbbrNSEMGXefTcQ/NUs5Yr7ouoLY9iJRXbGAYxD1z/2Ojg33836IuoJne7r7oqJJF9kMxqGWSkf+LOKPlbBbs46gfMVYJsTMsIogcr50bJhaJY8/uK0SGWUrnqZaza8oRI/IMV6rI3KuBsZKZPOWI0cJp0WRozVQqplEjBE6XAHgtkhkeEcf/vEiNJc72nV3SlJusmo4XzZ4J0Wi0yPH0xQqKW5aS6cJdAOSZAD5pP6Fr6ZdjWa1bcKL4vcphXU1ZtdXFGhX34Gg880Z9RsdevA4GRjcsTwja89PDLTIG9NGznzLyLYFizIi9p8HaqL758f1LY0erIX8a0mHMxr7su/U/ZdJ7R8LXTi7zKe+1AAAAABJRU5ErkJggg==";
|
|
32
|
+
|
|
33
|
+
function ensureOverlay() {
|
|
34
|
+
if (overlayHost && document.documentElement && document.documentElement.contains(overlayHost)) {
|
|
35
|
+
return overlayShadow;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
overlayHost = document.createElement("div");
|
|
39
|
+
overlayHost.id = "__chocolatito_overlay__";
|
|
40
|
+
// CRÍTICO: pointer-events: none en el host y en todos los hijos para que
|
|
41
|
+
// JAMÁS capture clics ni altere el flujo de interacción de la página o del agente.
|
|
42
|
+
// all: initial aísla de las reglas CSS de la página que pudieran pisarlo.
|
|
43
|
+
overlayHost.style.cssText =
|
|
44
|
+
"all: initial !important; position: fixed !important; inset: 0 !important; " +
|
|
45
|
+
"width: 100vw !important; height: 100vh !important; pointer-events: none !important; " +
|
|
46
|
+
"z-index: 2147483646 !important; overflow: hidden !important;";
|
|
47
|
+
|
|
48
|
+
overlayShadow = overlayHost.attachShadow({ mode: "closed" });
|
|
49
|
+
|
|
50
|
+
const style = document.createElement("style");
|
|
51
|
+
style.textContent = `
|
|
52
|
+
:host {
|
|
53
|
+
all: initial !important;
|
|
54
|
+
pointer-events: none !important;
|
|
55
|
+
}
|
|
56
|
+
* {
|
|
57
|
+
box-sizing: border-box !important;
|
|
58
|
+
pointer-events: none !important;
|
|
59
|
+
}
|
|
60
|
+
/* Borde terracota (#D97757) hecho con box-shadow inset: no desplaza el layout ni el scroll */
|
|
61
|
+
.frame {
|
|
62
|
+
position: fixed !important;
|
|
63
|
+
inset: 0 !important;
|
|
64
|
+
pointer-events: none !important;
|
|
65
|
+
box-shadow: inset 0 0 0 3px #D97757 !important;
|
|
66
|
+
z-index: 2147483646 !important;
|
|
67
|
+
display: none;
|
|
68
|
+
animation: chocoFramePulse 2s ease-in-out infinite;
|
|
69
|
+
}
|
|
70
|
+
.frame.visible {
|
|
71
|
+
display: block !important;
|
|
72
|
+
}
|
|
73
|
+
@keyframes chocoFramePulse {
|
|
74
|
+
0%, 100% { opacity: 1; }
|
|
75
|
+
50% { opacity: 0.6; }
|
|
76
|
+
}
|
|
77
|
+
@media (prefers-reduced-motion: reduce) {
|
|
78
|
+
.frame {
|
|
79
|
+
animation: none !important;
|
|
80
|
+
opacity: 1 !important;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/* Etiqueta distintiva en la esquina superior */
|
|
84
|
+
.badge {
|
|
85
|
+
position: fixed !important;
|
|
86
|
+
top: 12px !important;
|
|
87
|
+
right: 14px !important;
|
|
88
|
+
display: inline-flex !important;
|
|
89
|
+
align-items: center !important;
|
|
90
|
+
gap: 7px !important;
|
|
91
|
+
padding: 5px 10px !important;
|
|
92
|
+
background: #D97757 !important;
|
|
93
|
+
color: #FFFFFF !important;
|
|
94
|
+
font: 600 12px/1.3 -apple-system, "Segoe UI", system-ui, sans-serif !important;
|
|
95
|
+
border-radius: 6px !important;
|
|
96
|
+
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.35) !important;
|
|
97
|
+
pointer-events: none !important;
|
|
98
|
+
letter-spacing: 0.2px !important;
|
|
99
|
+
z-index: 2147483647 !important;
|
|
100
|
+
}
|
|
101
|
+
.badge-icon {
|
|
102
|
+
width: 16px !important;
|
|
103
|
+
height: 16px !important;
|
|
104
|
+
display: block !important;
|
|
105
|
+
flex: 0 0 16px !important;
|
|
106
|
+
}
|
|
107
|
+
/* Capa de efectos efímeros (recuadros de selección y ondas de clic) */
|
|
108
|
+
.layers {
|
|
109
|
+
position: fixed !important;
|
|
110
|
+
inset: 0 !important;
|
|
111
|
+
pointer-events: none !important;
|
|
112
|
+
z-index: 2147483647 !important;
|
|
113
|
+
}
|
|
114
|
+
.highlight-box {
|
|
115
|
+
position: fixed !important;
|
|
116
|
+
outline: 2px solid #D97757 !important;
|
|
117
|
+
background: rgba(217, 119, 87, 0.12) !important;
|
|
118
|
+
border-radius: 3px !important;
|
|
119
|
+
pointer-events: none !important;
|
|
120
|
+
transition: opacity 0.6s ease;
|
|
121
|
+
opacity: 1;
|
|
122
|
+
}
|
|
123
|
+
.highlight-box.fade-out {
|
|
124
|
+
opacity: 0 !important;
|
|
125
|
+
}
|
|
126
|
+
.highlight-tag {
|
|
127
|
+
position: absolute !important;
|
|
128
|
+
bottom: 100% !important;
|
|
129
|
+
left: 0 !important;
|
|
130
|
+
margin-bottom: 3px !important;
|
|
131
|
+
background: #D97757 !important;
|
|
132
|
+
color: #FFFFFF !important;
|
|
133
|
+
font: 600 11px/1.2 -apple-system, system-ui, sans-serif !important;
|
|
134
|
+
padding: 2px 6px !important;
|
|
135
|
+
border-radius: 3px !important;
|
|
136
|
+
white-space: nowrap !important;
|
|
137
|
+
pointer-events: none !important;
|
|
138
|
+
box-shadow: 0 2px 6px rgba(0,0,0,0.25) !important;
|
|
139
|
+
}
|
|
140
|
+
.click-ripple {
|
|
141
|
+
position: fixed !important;
|
|
142
|
+
width: 20px !important;
|
|
143
|
+
height: 20px !important;
|
|
144
|
+
border-radius: 50% !important;
|
|
145
|
+
background: rgba(217, 119, 87, 0.35) !important;
|
|
146
|
+
border: 2px solid #D97757 !important;
|
|
147
|
+
transform: translate(-50%, -50%) scale(0.3);
|
|
148
|
+
pointer-events: none !important;
|
|
149
|
+
animation: chocoRipple 0.4s ease-out forwards;
|
|
150
|
+
}
|
|
151
|
+
@keyframes chocoRipple {
|
|
152
|
+
0% { transform: translate(-50%, -50%) scale(0.3); opacity: 1; }
|
|
153
|
+
100% { transform: translate(-50%, -50%) scale(2.2); opacity: 0; }
|
|
154
|
+
}
|
|
155
|
+
`;
|
|
156
|
+
|
|
157
|
+
overlayShadow.appendChild(style);
|
|
158
|
+
|
|
159
|
+
// Solo el frame principal de la ventana (IS_TOP) dibuja el borde exterior y la insignia
|
|
160
|
+
// para no llenar de bordes anidados los iframes de la página.
|
|
161
|
+
if (IS_TOP) {
|
|
162
|
+
overlayFrame = document.createElement("div");
|
|
163
|
+
overlayFrame.className = "frame" + (overlayActivo ? " visible" : "");
|
|
164
|
+
overlayFrame.innerHTML = `
|
|
165
|
+
<div class="badge">
|
|
166
|
+
<img class="badge-icon" src="${LOGO_16_DATA_URI}" alt="" />
|
|
167
|
+
<span>Chocolatito está trabajando</span>
|
|
168
|
+
</div>
|
|
169
|
+
`;
|
|
170
|
+
overlayShadow.appendChild(overlayFrame);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
overlayLayers = document.createElement("div");
|
|
174
|
+
overlayLayers.className = "layers";
|
|
175
|
+
overlayShadow.appendChild(overlayLayers);
|
|
176
|
+
|
|
177
|
+
(document.body || document.documentElement).appendChild(overlayHost);
|
|
178
|
+
return overlayShadow;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function setOverlayActive(activo) {
|
|
182
|
+
overlayActivo = Boolean(activo);
|
|
183
|
+
ensureOverlay();
|
|
184
|
+
if (overlayFrame) {
|
|
185
|
+
if (overlayActivo) overlayFrame.classList.add("visible");
|
|
186
|
+
else overlayFrame.classList.remove("visible");
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function isOverlayActive() {
|
|
191
|
+
return overlayActivo;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Dibuja un recuadro sobre el elemento objetivo antes de actuar (C1) */
|
|
195
|
+
function resaltarElemento(el, ref, role, name) {
|
|
196
|
+
try {
|
|
197
|
+
ensureOverlay();
|
|
198
|
+
if (!overlayLayers) return;
|
|
199
|
+
const r = el.getBoundingClientRect();
|
|
200
|
+
if (r.width < 1 && r.height < 1) return;
|
|
201
|
+
|
|
202
|
+
const box = document.createElement("div");
|
|
203
|
+
box.className = "highlight-box";
|
|
204
|
+
box.style.left = `${Math.round(r.left)}px`;
|
|
205
|
+
box.style.top = `${Math.round(r.top)}px`;
|
|
206
|
+
box.style.width = `${Math.round(r.width)}px`;
|
|
207
|
+
box.style.height = `${Math.round(r.height)}px`;
|
|
208
|
+
|
|
209
|
+
const tag = document.createElement("div");
|
|
210
|
+
tag.className = "highlight-tag";
|
|
211
|
+
const etiqueta = `[#${ref}] ${role || ""}${name ? ' "' + name.slice(0, 30) + '"' : ""}`;
|
|
212
|
+
tag.textContent = etiqueta.trim();
|
|
213
|
+
box.appendChild(tag);
|
|
214
|
+
|
|
215
|
+
overlayLayers.appendChild(box);
|
|
216
|
+
|
|
217
|
+
setTimeout(() => {
|
|
218
|
+
box.classList.add("fade-out");
|
|
219
|
+
setTimeout(() => {
|
|
220
|
+
try { if (box.parentNode) box.parentNode.removeChild(box); } catch (_) {}
|
|
221
|
+
}, 600);
|
|
222
|
+
}, 400);
|
|
223
|
+
} catch (_) {}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Dibuja una onda en el punto exacto donde se pulsó (C2) */
|
|
227
|
+
function mostrarOndaClic(x, y) {
|
|
228
|
+
try {
|
|
229
|
+
ensureOverlay();
|
|
230
|
+
if (!overlayLayers) return;
|
|
231
|
+
const ripple = document.createElement("div");
|
|
232
|
+
ripple.className = "click-ripple";
|
|
233
|
+
ripple.style.left = `${Math.round(x)}px`;
|
|
234
|
+
ripple.style.top = `${Math.round(y)}px`;
|
|
235
|
+
|
|
236
|
+
overlayLayers.appendChild(ripple);
|
|
237
|
+
|
|
238
|
+
setTimeout(() => {
|
|
239
|
+
try { if (ripple.parentNode) ripple.parentNode.removeChild(ripple); } catch (_) {}
|
|
240
|
+
}, 400);
|
|
241
|
+
} catch (_) {}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// D3: Observador de mutaciones para saber cuándo la pantalla "está quieta" (reposo DOM)
|
|
245
|
+
let ultimaMutacion = Date.now();
|
|
246
|
+
try {
|
|
247
|
+
const mutObs = new MutationObserver(() => {
|
|
248
|
+
ultimaMutacion = Date.now();
|
|
249
|
+
});
|
|
250
|
+
mutObs.observe(document.documentElement || document, {
|
|
251
|
+
childList: true,
|
|
252
|
+
subtree: true,
|
|
253
|
+
attributes: true,
|
|
254
|
+
characterData: true,
|
|
255
|
+
});
|
|
256
|
+
} catch (_) {}
|
|
257
|
+
|
|
258
|
+
// ======================================================== aviso flotante inferior (banner)
|
|
21
259
|
|
|
22
260
|
let bannerHost = null;
|
|
23
261
|
let bannerBody = null;
|
|
@@ -29,8 +267,6 @@
|
|
|
29
267
|
|
|
30
268
|
bannerHost = document.createElement("div");
|
|
31
269
|
bannerHost.id = "__chocolatito_banner__";
|
|
32
|
-
// Shadow DOM: los estilos de la pagina no pueden romper el aviso, ni el
|
|
33
|
-
// aviso ensuciar la pagina.
|
|
34
270
|
const shadow = bannerHost.attachShadow({ mode: "closed" });
|
|
35
271
|
const style = document.createElement("style");
|
|
36
272
|
style.textContent = `
|
|
@@ -84,7 +320,6 @@
|
|
|
84
320
|
bannerBody.action.textContent = text || "";
|
|
85
321
|
bannerBody.wrap.classList.remove("hide");
|
|
86
322
|
clearTimeout(bannerHideTimer);
|
|
87
|
-
// Se desvanece solo si el agente deja de trabajar, para no molestar.
|
|
88
323
|
bannerHideTimer = setTimeout(() => {
|
|
89
324
|
if (bannerBody) bannerBody.wrap.classList.add("hide");
|
|
90
325
|
}, 12000);
|
|
@@ -155,12 +390,43 @@
|
|
|
155
390
|
function norm(s) {
|
|
156
391
|
return (s || "")
|
|
157
392
|
.normalize("NFD")
|
|
393
|
+
// Rango de diacriticos combinantes. Escrito con escapes y no con los
|
|
394
|
+
// caracteres literales: en el fuente eran invisibles y cualquier editor
|
|
395
|
+
// que "normalizara" el archivo se los llevaba por delante sin que nadie
|
|
396
|
+
// lo notara hasta que el filtro dejaba de casar acentos.
|
|
158
397
|
.replace(/[̀-ͯ]/g, "")
|
|
159
398
|
.toLowerCase();
|
|
160
399
|
}
|
|
161
400
|
|
|
162
|
-
|
|
163
|
-
|
|
401
|
+
/**
|
|
402
|
+
* Quita las marcas del snapshot anterior.
|
|
403
|
+
*
|
|
404
|
+
* data-choco-ref se escribia en los elementos de la pagina del usuario y no
|
|
405
|
+
* lo borraba nadie: los atributos se acumulaban snapshot tras snapshot, y un
|
|
406
|
+
* ref viejo podia seguir resolviendo a un elemento que ya no estaba en la
|
|
407
|
+
* lista actual.
|
|
408
|
+
*/
|
|
409
|
+
function limpiarMarcas() {
|
|
410
|
+
for (const root of allRoots()) {
|
|
411
|
+
let marcados;
|
|
412
|
+
try {
|
|
413
|
+
marcados = root.querySelectorAll("[data-choco-ref]");
|
|
414
|
+
} catch (_) {
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
for (const el of marcados) el.removeAttribute("data-choco-ref");
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function buildSnapshot(startIndex, filter, nuevo) {
|
|
422
|
+
// Antes esto era `startIndex === 0`, que solo es cierto en el PRIMER frame.
|
|
423
|
+
// Cada frame tiene su propio content script con su propia tabla, asi que
|
|
424
|
+
// los subframes no la limpiaban nunca: acumulaban identidades de snapshots
|
|
425
|
+
// viejos y un ref podia resolver a un boton que ya no era ese.
|
|
426
|
+
if (nuevo || startIndex === 0) {
|
|
427
|
+
refIdentity.clear();
|
|
428
|
+
limpiarMarcas();
|
|
429
|
+
}
|
|
164
430
|
const out = [];
|
|
165
431
|
let idx = startIndex;
|
|
166
432
|
const f = norm(filter);
|
|
@@ -213,19 +479,41 @@
|
|
|
213
479
|
*/
|
|
214
480
|
const refIdentity = new Map();
|
|
215
481
|
|
|
216
|
-
|
|
482
|
+
/**
|
|
483
|
+
* Busca elementos en el DOM que tengan el mismo rol o nombre similar
|
|
484
|
+
* para dar mensajes de error útiles si un ref no se encuentra (D5).
|
|
485
|
+
*/
|
|
486
|
+
function buscarSimilares(id) {
|
|
487
|
+
if (!id) return [];
|
|
488
|
+
const similares = [];
|
|
217
489
|
for (const root of allRoots()) {
|
|
218
|
-
|
|
219
|
-
|
|
490
|
+
let nodes;
|
|
491
|
+
try { nodes = root.querySelectorAll(SELECTOR); } catch (_) { continue; }
|
|
492
|
+
for (const el of nodes) {
|
|
493
|
+
const r = roleOf(el);
|
|
494
|
+
const n = accessibleName(el).slice(0, 60);
|
|
495
|
+
if (!n && r !== "textbox") continue;
|
|
496
|
+
const coincideRol = id.role && r === id.role;
|
|
497
|
+
const coincideNombre = id.name && n && (norm(n).includes(norm(id.name)) || norm(id.name).includes(norm(n)));
|
|
498
|
+
if (coincideRol || coincideNombre) {
|
|
499
|
+
similares.push({ role: r, name: n });
|
|
500
|
+
if (similares.length >= 3) return similares;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
220
503
|
}
|
|
504
|
+
return similares;
|
|
505
|
+
}
|
|
221
506
|
|
|
222
|
-
|
|
223
|
-
|
|
507
|
+
/**
|
|
508
|
+
* Reubica un elemento tras re-renders de frameworks reactivos (React/Vue/Flow)
|
|
509
|
+
* buscando candidatos con la misma identidad registrada.
|
|
510
|
+
*/
|
|
511
|
+
function reubicarPorIdentidad(id, ref) {
|
|
224
512
|
if (!id) return null;
|
|
225
|
-
|
|
226
513
|
const candidatos = [];
|
|
227
514
|
for (const root of allRoots()) {
|
|
228
|
-
|
|
515
|
+
let nodes;
|
|
516
|
+
try { nodes = root.querySelectorAll(SELECTOR); } catch (_) { continue; }
|
|
229
517
|
for (const el of nodes) {
|
|
230
518
|
if (roleOf(el) !== id.role) continue;
|
|
231
519
|
if (accessibleName(el).slice(0, 120) !== id.name) continue;
|
|
@@ -235,13 +523,68 @@
|
|
|
235
523
|
}
|
|
236
524
|
}
|
|
237
525
|
if (candidatos.length === 0) return null;
|
|
238
|
-
|
|
239
|
-
// Con varios iguales se elige el que ocupaba la misma posicion en la lista.
|
|
240
526
|
const elegido = candidatos[Math.min(id.idx, candidatos.length - 1)];
|
|
241
527
|
elegido.setAttribute("data-choco-ref", String(ref));
|
|
242
528
|
return elegido;
|
|
243
529
|
}
|
|
244
530
|
|
|
531
|
+
/**
|
|
532
|
+
* Resuelve un ref verificando que el elemento siga siendo el mismo (D4).
|
|
533
|
+
* Si el DOM mutó drásticamente o el elemento desapareció, devuelve un error
|
|
534
|
+
* explicativo con sugerencias de elementos similares (D5).
|
|
535
|
+
*/
|
|
536
|
+
function resolverRef(ref) {
|
|
537
|
+
const id = refIdentity.get(ref);
|
|
538
|
+
let el = null;
|
|
539
|
+
for (const root of allRoots()) {
|
|
540
|
+
try {
|
|
541
|
+
el = root.querySelector('[data-choco-ref="' + ref + '"]');
|
|
542
|
+
if (el) break;
|
|
543
|
+
} catch (_) {}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (el) {
|
|
547
|
+
if (id) {
|
|
548
|
+
const actualRole = roleOf(el);
|
|
549
|
+
const actualName = accessibleName(el).slice(0, 120);
|
|
550
|
+
const rolCoincide = actualRole === id.role;
|
|
551
|
+
const nombreCoincide = !id.name || !actualName || norm(actualName).includes(norm(id.name)) || norm(id.name).includes(norm(actualName));
|
|
552
|
+
if (!rolCoincide && !nombreCoincide) {
|
|
553
|
+
// El elemento fue reciclado por otro componente del framework; reintentar por identidad
|
|
554
|
+
const reubicado = reubicarPorIdentidad(id, ref);
|
|
555
|
+
if (reubicado) return { ok: true, el: reubicado, id };
|
|
556
|
+
const similares = buscarSimilares(id);
|
|
557
|
+
const simTxt = similares.length > 0 ? ` Elementos parecidos: ${similares.map(s => `<${s.role}> "${s.name}"`).join(", ")}.` : "";
|
|
558
|
+
return {
|
|
559
|
+
ok: false,
|
|
560
|
+
error: `El DOM cambió desde el último snapshot: el ref [${ref}] apuntaba a <${id.role}> "${id.name}", pero ahora hay <${actualRole}> "${actualName}".${simTxt} Reház el snapshot antes de continuar.`,
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
return { ok: true, el, id };
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Si el nodo desapareció en un re-render, se busca por identidad
|
|
568
|
+
if (id) {
|
|
569
|
+
const reubicado = reubicarPorIdentidad(id, ref);
|
|
570
|
+
if (reubicado) return { ok: true, el: reubicado, id };
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const similares = buscarSimilares(id);
|
|
574
|
+
const simTxt = similares.length > 0
|
|
575
|
+
? ` Elementos similares disponibles: ${similares.map(s => `<${s.role}> "${s.name}"`).join(", ")}.`
|
|
576
|
+
: " No se encontraron elementos similares en el DOM.";
|
|
577
|
+
return {
|
|
578
|
+
ok: false,
|
|
579
|
+
error: `Ref [${ref}] no encontrado en este frame. Se buscaba: <${id?.role || "elemento"}> "${id?.name || ""}".${simTxt} Reház el snapshot para actualizar los refs.`,
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function findByRef(ref) {
|
|
584
|
+
const res = resolverRef(ref);
|
|
585
|
+
return res.ok ? res.el : null;
|
|
586
|
+
}
|
|
587
|
+
|
|
245
588
|
// ======================================================== acciones
|
|
246
589
|
|
|
247
590
|
function centerOf(el) {
|
|
@@ -249,9 +592,52 @@
|
|
|
249
592
|
return { x: r.left + r.width / 2, y: r.top + r.height / 2 };
|
|
250
593
|
}
|
|
251
594
|
|
|
595
|
+
/** Maneja de forma nativa la selección en menús desplegables <select> (D6) */
|
|
596
|
+
function seleccionarOpcion(selectEl, textoOValor) {
|
|
597
|
+
const needle = norm(textoOValor);
|
|
598
|
+
let elegidoIdx = -1;
|
|
599
|
+
for (let i = 0; i < selectEl.options.length; i++) {
|
|
600
|
+
const opt = selectEl.options[i];
|
|
601
|
+
if (norm(opt.value) === needle || norm(opt.text).includes(needle) || norm(opt.label).includes(needle)) {
|
|
602
|
+
elegidoIdx = i;
|
|
603
|
+
break;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
if (elegidoIdx === -1) {
|
|
607
|
+
const num = Number(textoOValor);
|
|
608
|
+
if (!isNaN(num) && num >= 0 && num < selectEl.options.length) elegidoIdx = num;
|
|
609
|
+
}
|
|
610
|
+
if (elegidoIdx !== -1) {
|
|
611
|
+
selectEl.selectedIndex = elegidoIdx;
|
|
612
|
+
const valor = selectEl.options[elegidoIdx].value;
|
|
613
|
+
const proto = HTMLSelectElement.prototype;
|
|
614
|
+
const desc = Object.getOwnPropertyDescriptor(proto, "value");
|
|
615
|
+
if (desc && desc.set) desc.set.call(selectEl, valor);
|
|
616
|
+
else selectEl.value = valor;
|
|
617
|
+
}
|
|
618
|
+
selectEl.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
|
|
619
|
+
selectEl.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
|
|
620
|
+
return selectEl.value;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/** Conmuta casillas de verificación y radios usando descriptors nativos y eventos (D6) */
|
|
624
|
+
function conmutarCasilla(inputEl, estadoDeseado) {
|
|
625
|
+
const proximo = estadoDeseado !== undefined ? Boolean(estadoDeseado) : (inputEl.type === "checkbox" ? !inputEl.checked : true);
|
|
626
|
+
const proto = HTMLInputElement.prototype;
|
|
627
|
+
const desc = Object.getOwnPropertyDescriptor(proto, "checked");
|
|
628
|
+
if (desc && desc.set) desc.set.call(inputEl, proximo);
|
|
629
|
+
else inputEl.checked = proximo;
|
|
630
|
+
|
|
631
|
+
inputEl.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true, composed: true }));
|
|
632
|
+
inputEl.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
|
|
633
|
+
inputEl.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
|
|
634
|
+
return inputEl.checked;
|
|
635
|
+
}
|
|
636
|
+
|
|
252
637
|
/**
|
|
253
|
-
* Pulsa sin
|
|
638
|
+
* Pulsa sin ratón real. Se emite la secuencia completa de eventos porque
|
|
254
639
|
* muchos componentes escuchan pointerdown/mouseup y no solo click.
|
|
640
|
+
* Realiza scroll al centro antes de pulsar (C3) y muestra onda de clic (C2).
|
|
255
641
|
*/
|
|
256
642
|
function clickElement(el) {
|
|
257
643
|
try {
|
|
@@ -260,6 +646,12 @@
|
|
|
260
646
|
try { el.scrollIntoView(); } catch (_) {}
|
|
261
647
|
}
|
|
262
648
|
const c = centerOf(el);
|
|
649
|
+
mostrarOndaClic(c.x, c.y);
|
|
650
|
+
|
|
651
|
+
if (el.type === "checkbox" || el.type === "radio") {
|
|
652
|
+
return conmutarCasilla(el);
|
|
653
|
+
}
|
|
654
|
+
|
|
263
655
|
const base = { bubbles: true, cancelable: true, composed: true, view: window, clientX: c.x, clientY: c.y, button: 0 };
|
|
264
656
|
const ptr = Object.assign({ pointerId: 1, isPrimary: true, pointerType: "mouse" }, base);
|
|
265
657
|
|
|
@@ -277,7 +669,7 @@
|
|
|
277
669
|
}
|
|
278
670
|
|
|
279
671
|
/**
|
|
280
|
-
* Escribe de forma que React se
|
|
672
|
+
* Escribe de forma que React/Vue se enteren.
|
|
281
673
|
*
|
|
282
674
|
* Asignar el.value directamente NO sirve: React guarda su propio estado y no
|
|
283
675
|
* ve el cambio, asi que el campo parece relleno pero el boton de enviar sigue
|
|
@@ -299,6 +691,16 @@
|
|
|
299
691
|
try { el.scrollIntoView({ block: "center", behavior: "instant" }); } catch (_) {}
|
|
300
692
|
try { el.focus({ preventScroll: true }); } catch (_) {}
|
|
301
693
|
|
|
694
|
+
// Soporte nativo para <select> (D6)
|
|
695
|
+
if (el.tagName.toLowerCase() === "select") {
|
|
696
|
+
return seleccionarOpcion(el, text);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// Soporte para checkboxes y radios (D6)
|
|
700
|
+
if (el.type === "checkbox" || el.type === "radio") {
|
|
701
|
+
return conmutarCasilla(el);
|
|
702
|
+
}
|
|
703
|
+
|
|
302
704
|
if (el.isContentEditable) {
|
|
303
705
|
if (replace) {
|
|
304
706
|
const sel = window.getSelection();
|
|
@@ -307,7 +709,6 @@
|
|
|
307
709
|
sel.removeAllRanges();
|
|
308
710
|
sel.addRange(range);
|
|
309
711
|
}
|
|
310
|
-
// execCommand emite beforeinput/input igual que si se hubiera tecleado.
|
|
311
712
|
const ok = document.execCommand("insertText", false, text);
|
|
312
713
|
if (!ok) {
|
|
313
714
|
el.textContent = replace ? text : (el.textContent || "") + text;
|
|
@@ -324,15 +725,46 @@
|
|
|
324
725
|
return true;
|
|
325
726
|
}
|
|
326
727
|
|
|
728
|
+
/**
|
|
729
|
+
* Codigos de tecla.
|
|
730
|
+
*/
|
|
731
|
+
const CODIGOS = {
|
|
732
|
+
Enter: 13, Escape: 27, Esc: 27, Tab: 9, Backspace: 8, Delete: 46,
|
|
733
|
+
ArrowUp: 38, ArrowDown: 40, ArrowLeft: 37, ArrowRight: 39,
|
|
734
|
+
Home: 36, End: 35, PageUp: 33, PageDown: 34, Insert: 45,
|
|
735
|
+
" ": 32, Space: 32, Shift: 16, Control: 17, Alt: 18,
|
|
736
|
+
F1: 112, F2: 113, F3: 114, F4: 115, F5: 116, F6: 117,
|
|
737
|
+
F7: 118, F8: 119, F9: 120, F10: 121, F11: 122, F12: 123,
|
|
738
|
+
};
|
|
739
|
+
|
|
740
|
+
/** Nombre DOM del codigo fisico ("KeyA", "Digit1", "Space"...). */
|
|
741
|
+
function codigoFisico(key) {
|
|
742
|
+
if (key === " " || key === "Space") return "Space";
|
|
743
|
+
if (/^[a-zA-Z]$/.test(key)) return "Key" + key.toUpperCase();
|
|
744
|
+
if (/^[0-9]$/.test(key)) return "Digit" + key;
|
|
745
|
+
return key;
|
|
746
|
+
}
|
|
747
|
+
|
|
327
748
|
function pressKey(el, key) {
|
|
328
749
|
const target = el || document.activeElement || document.body;
|
|
329
|
-
const
|
|
330
|
-
const code =
|
|
331
|
-
|
|
750
|
+
const nombre = key === "Space" ? " " : key;
|
|
751
|
+
const code = CODIGOS[key] !== undefined
|
|
752
|
+
? CODIGOS[key]
|
|
753
|
+
: nombre.length === 1
|
|
754
|
+
? nombre.toUpperCase().charCodeAt(0)
|
|
755
|
+
: 0;
|
|
756
|
+
const init = {
|
|
757
|
+
key: nombre,
|
|
758
|
+
code: codigoFisico(key),
|
|
759
|
+
keyCode: code,
|
|
760
|
+
which: code,
|
|
761
|
+
bubbles: true,
|
|
762
|
+
cancelable: true,
|
|
763
|
+
composed: true,
|
|
764
|
+
};
|
|
332
765
|
target.dispatchEvent(new KeyboardEvent("keydown", init));
|
|
333
|
-
target.dispatchEvent(new KeyboardEvent("keypress", init));
|
|
766
|
+
if (nombre.length === 1) target.dispatchEvent(new KeyboardEvent("keypress", init));
|
|
334
767
|
target.dispatchEvent(new KeyboardEvent("keyup", init));
|
|
335
|
-
// Si es un formulario y la tecla es Enter, se intenta enviar de verdad.
|
|
336
768
|
if (key === "Enter" && target.form && typeof target.form.requestSubmit === "function") {
|
|
337
769
|
try { target.form.requestSubmit(); } catch (_) {}
|
|
338
770
|
}
|
|
@@ -343,9 +775,16 @@
|
|
|
343
775
|
|
|
344
776
|
chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
|
|
345
777
|
try {
|
|
346
|
-
|
|
778
|
+
const comando = msg.cmd || msg.tipo;
|
|
779
|
+
switch (comando) {
|
|
780
|
+
case "overlay": {
|
|
781
|
+
setOverlayActive(msg.activo === true);
|
|
782
|
+
sendResponse({ ok: true, activo: isOverlayActive() });
|
|
783
|
+
break;
|
|
784
|
+
}
|
|
785
|
+
|
|
347
786
|
case "ping":
|
|
348
|
-
sendResponse({ ok: true, top: IS_TOP, url: location.href });
|
|
787
|
+
sendResponse({ ok: true, top: IS_TOP, url: location.href, overlay: isOverlayActive() });
|
|
349
788
|
break;
|
|
350
789
|
|
|
351
790
|
case "banner":
|
|
@@ -355,52 +794,57 @@
|
|
|
355
794
|
break;
|
|
356
795
|
|
|
357
796
|
case "snapshot": {
|
|
358
|
-
const items = buildSnapshot(msg.startIndex || 0, msg.filter || "");
|
|
797
|
+
const items = buildSnapshot(msg.startIndex || 0, msg.filter || "", msg.nuevo === true);
|
|
359
798
|
sendResponse({ ok: true, items, url: location.href, title: document.title, top: IS_TOP });
|
|
360
799
|
break;
|
|
361
800
|
}
|
|
362
801
|
|
|
363
802
|
case "click": {
|
|
364
|
-
const
|
|
365
|
-
if (!
|
|
803
|
+
const resolucion = resolverRef(msg.ref);
|
|
804
|
+
if (!resolucion.ok) { sendResponse(resolucion); break; }
|
|
805
|
+
const el = resolucion.el;
|
|
366
806
|
const label = accessibleName(el).slice(0, 60);
|
|
807
|
+
resaltarElemento(el, msg.ref, resolucion.id?.role, label);
|
|
367
808
|
clickElement(el);
|
|
368
809
|
sendResponse({ ok: true, label });
|
|
369
810
|
break;
|
|
370
811
|
}
|
|
371
812
|
|
|
372
813
|
case "type": {
|
|
373
|
-
const
|
|
374
|
-
if (!
|
|
814
|
+
const resolucion = resolverRef(msg.ref);
|
|
815
|
+
if (!resolucion.ok) { sendResponse(resolucion); break; }
|
|
816
|
+
const el = resolucion.el;
|
|
817
|
+
const label = accessibleName(el).slice(0, 60);
|
|
818
|
+
resaltarElemento(el, msg.ref, resolucion.id?.role, label);
|
|
375
819
|
typeInto(el, msg.text, msg.replace !== false);
|
|
376
|
-
// Se devuelve el valor real tras escribir, para poder verificar.
|
|
377
820
|
const after = el.isContentEditable ? (el.textContent || "") : (el.value || "");
|
|
378
821
|
if (msg.submit) pressKey(el, "Enter");
|
|
379
|
-
sendResponse({ ok: true, value: String(after).slice(0, 200) });
|
|
822
|
+
sendResponse({ ok: true, value: String(after).slice(0, 200), label });
|
|
380
823
|
break;
|
|
381
824
|
}
|
|
382
825
|
|
|
383
|
-
// Devuelve el centro del elemento en coordenadas del viewport, que es lo
|
|
384
|
-
// que necesita el protocolo DevTools para entregar un clic real.
|
|
385
826
|
case "rect": {
|
|
386
|
-
const
|
|
387
|
-
if (!
|
|
388
|
-
|
|
827
|
+
const resolucion = resolverRef(msg.ref);
|
|
828
|
+
if (!resolucion.ok) { sendResponse(resolucion); break; }
|
|
829
|
+
const el = resolucion.el;
|
|
830
|
+
try { el.scrollIntoView({ block: "center", inline: "center", behavior: "instant" }); } catch (_) {}
|
|
831
|
+
const label = accessibleName(el).slice(0, 60);
|
|
832
|
+
resaltarElemento(el, msg.ref, resolucion.id?.role, label);
|
|
389
833
|
const r = el.getBoundingClientRect();
|
|
390
834
|
sendResponse({
|
|
391
835
|
ok: true,
|
|
392
836
|
x: Math.round(r.left + r.width / 2),
|
|
393
837
|
y: Math.round(r.top + r.height / 2),
|
|
394
|
-
label
|
|
395
|
-
editable: el.isContentEditable || "value" in el,
|
|
838
|
+
label,
|
|
839
|
+
editable: el.isContentEditable || "value" in el || el.tagName.toLowerCase() === "select",
|
|
396
840
|
});
|
|
397
841
|
break;
|
|
398
842
|
}
|
|
399
843
|
|
|
400
|
-
// Solo lee el valor actual, para verificar despues de escribir.
|
|
401
844
|
case "value": {
|
|
402
|
-
const
|
|
403
|
-
if (!
|
|
845
|
+
const resolucion = resolverRef(msg.ref);
|
|
846
|
+
if (!resolucion.ok) { sendResponse(resolucion); break; }
|
|
847
|
+
const el = resolucion.el;
|
|
404
848
|
sendResponse({
|
|
405
849
|
ok: true,
|
|
406
850
|
value: String(el.isContentEditable ? el.textContent || "" : el.value || "").slice(0, 300),
|
|
@@ -409,12 +853,39 @@
|
|
|
409
853
|
}
|
|
410
854
|
|
|
411
855
|
case "press": {
|
|
412
|
-
|
|
856
|
+
let el = null;
|
|
857
|
+
if (msg.ref !== undefined && msg.ref !== null) {
|
|
858
|
+
const resolucion = resolverRef(msg.ref);
|
|
859
|
+
if (resolucion.ok) {
|
|
860
|
+
el = resolucion.el;
|
|
861
|
+
resaltarElemento(el, msg.ref, resolucion.id?.role, accessibleName(el));
|
|
862
|
+
}
|
|
863
|
+
}
|
|
413
864
|
pressKey(el, msg.keys || "Enter");
|
|
414
865
|
sendResponse({ ok: true });
|
|
415
866
|
break;
|
|
416
867
|
}
|
|
417
868
|
|
|
869
|
+
case "waitForCondition": {
|
|
870
|
+
if (msg.selector) {
|
|
871
|
+
let found = false;
|
|
872
|
+
for (const root of allRoots()) {
|
|
873
|
+
try {
|
|
874
|
+
if (root.querySelector(msg.selector)) { found = true; break; }
|
|
875
|
+
} catch (_) {}
|
|
876
|
+
}
|
|
877
|
+
sendResponse({ ok: true, conditionMet: found });
|
|
878
|
+
break;
|
|
879
|
+
}
|
|
880
|
+
if (msg.stable) {
|
|
881
|
+
const quietFor = Date.now() - ultimaMutacion;
|
|
882
|
+
sendResponse({ ok: true, conditionMet: quietFor >= (msg.quietMs || 400) });
|
|
883
|
+
break;
|
|
884
|
+
}
|
|
885
|
+
sendResponse({ ok: true, conditionMet: true });
|
|
886
|
+
break;
|
|
887
|
+
}
|
|
888
|
+
|
|
418
889
|
case "getText":
|
|
419
890
|
sendResponse({
|
|
420
891
|
ok: true,
|
|
@@ -427,16 +898,26 @@
|
|
|
427
898
|
case "eval": {
|
|
428
899
|
let out;
|
|
429
900
|
try {
|
|
430
|
-
|
|
901
|
+
const valor = eval(msg.script);
|
|
902
|
+
if (valor === undefined) out = "undefined";
|
|
903
|
+
else if (valor === null) out = "null";
|
|
904
|
+
else if (typeof valor === "object") {
|
|
905
|
+
try {
|
|
906
|
+
out = JSON.stringify(valor, null, 2);
|
|
907
|
+
if (out === undefined) out = String(valor);
|
|
908
|
+
} catch (_) {
|
|
909
|
+
out = String(valor);
|
|
910
|
+
}
|
|
911
|
+
} else out = String(valor);
|
|
431
912
|
} catch (e) {
|
|
432
913
|
out = "Error JS: " + (e && e.message ? e.message : e);
|
|
433
914
|
}
|
|
434
|
-
sendResponse({ ok: true, result: out });
|
|
915
|
+
sendResponse({ ok: true, result: String(out).slice(0, 20000) });
|
|
435
916
|
break;
|
|
436
917
|
}
|
|
437
918
|
|
|
438
919
|
default:
|
|
439
|
-
sendResponse({ ok: false, error: "comando desconocido: " + msg.cmd });
|
|
920
|
+
sendResponse({ ok: false, error: "comando desconocido: " + (msg.cmd || msg.tipo) });
|
|
440
921
|
}
|
|
441
922
|
} catch (err) {
|
|
442
923
|
sendResponse({ ok: false, error: String((err && err.message) || err) });
|