veo-sdk 0.6.1 → 0.6.3
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/dist/builder-PPLIAUCM.mjs +5 -0
- package/dist/{builder-BKPMANUB.mjs.map → builder-PPLIAUCM.mjs.map} +1 -1
- package/dist/builder.cjs +641 -105
- package/dist/builder.cjs.map +1 -1
- package/dist/builder.d.cts +78 -8
- package/dist/builder.d.ts +78 -8
- package/dist/builder.mjs +4 -4
- package/dist/{chunk-GVPAJFNW.mjs → chunk-3JEAPZ6A.mjs} +3 -3
- package/dist/{chunk-GVPAJFNW.mjs.map → chunk-3JEAPZ6A.mjs.map} +1 -1
- package/dist/{chunk-VWF4UHUU.mjs → chunk-DU6I4ZU2.mjs} +284 -15
- package/dist/chunk-DU6I4ZU2.mjs.map +1 -0
- package/dist/{chunk-NGP7IJVE.mjs → chunk-SZCIXA5X.mjs} +357 -82
- package/dist/chunk-SZCIXA5X.mjs.map +1 -0
- package/dist/detect-theme-X4K3TLUL.mjs +446 -0
- package/dist/detect-theme-X4K3TLUL.mjs.map +1 -0
- package/dist/{guide-preview-DylsEhrg.d.cts → guide-preview-DEFhfA4o.d.cts} +115 -0
- package/dist/{guide-preview-DylsEhrg.d.ts → guide-preview-DEFhfA4o.d.ts} +115 -0
- package/dist/index.cjs +1515 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -1
- package/dist/index.d.ts +18 -1
- package/dist/index.mjs +442 -17
- package/dist/index.mjs.map +1 -1
- package/dist/veo-builder.js +132 -25
- package/dist/veo-builder.js.map +1 -1
- package/dist/veo-guides.js +84 -12
- package/dist/veo-guides.js.map +1 -1
- package/dist/veo-theme.js +2 -0
- package/dist/veo-theme.js.map +1 -0
- package/dist/veo.js +2 -2
- package/dist/veo.js.map +1 -1
- package/package.json +2 -2
- package/dist/builder-BKPMANUB.mjs +0 -5
- package/dist/chunk-NGP7IJVE.mjs.map +0 -1
- package/dist/chunk-VWF4UHUU.mjs.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { P as PreviewGuideInput, a as PreviewHandle, b as PreviewResult, c as closeGuidePreview, p as previewGuide } from './guide-preview-
|
|
1
|
+
export { P as PreviewGuideInput, a as PreviewHandle, b as PreviewResult, c as closeGuidePreview, p as previewGuide } from './guide-preview-DEFhfA4o.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Configuración de la autocaptura de interacciones del DOM.
|
|
@@ -26,6 +26,11 @@ interface VeoAutocaptureConfig {
|
|
|
26
26
|
captureElementMetadata?: boolean;
|
|
27
27
|
/** Niveles de ancestros a capturar. Default: 5. Max: 10. */
|
|
28
28
|
captureAncestors?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Calidad de los clics: emite `$dead_click` (clic sin reacción), `$rage_click`
|
|
31
|
+
* (ráfaga de clics) y `$click_error` (clic seguido de un error). Default: true.
|
|
32
|
+
*/
|
|
33
|
+
captureClickQuality?: boolean;
|
|
29
34
|
}
|
|
30
35
|
/**
|
|
31
36
|
* Configuración del cliente Veo.
|
|
@@ -49,6 +54,18 @@ interface VeoConfig {
|
|
|
49
54
|
* tooltips. Default: `false`.
|
|
50
55
|
*/
|
|
51
56
|
guides?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Detectar el tema visual de la app (colores base en claro/oscuro y fuentes)
|
|
59
|
+
* para que el design system de OnUser arranque con la marca del cliente.
|
|
60
|
+
* Solo viajan colores y nombres de fuentes, como mucho una vez al día por
|
|
61
|
+
* navegador. Default: `true`; `false` lo deshabilita.
|
|
62
|
+
*/
|
|
63
|
+
detectTheme?: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* URL del bundle del detector de tema (`veo-theme.js`). Solo aplica al build
|
|
66
|
+
* IIFE; si se omite, se deriva del `<script>` que cargó `veo.js`.
|
|
67
|
+
*/
|
|
68
|
+
themeUrl?: string;
|
|
52
69
|
/**
|
|
53
70
|
* URL del bundle del builder (`veo-builder.js`). Solo se carga cuando la
|
|
54
71
|
* página trae `?veoBuilder=<token>` (modo edición desde el dashboard). Si
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { P as PreviewGuideInput, a as PreviewHandle, b as PreviewResult, c as closeGuidePreview, p as previewGuide } from './guide-preview-
|
|
1
|
+
export { P as PreviewGuideInput, a as PreviewHandle, b as PreviewResult, c as closeGuidePreview, p as previewGuide } from './guide-preview-DEFhfA4o.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Configuración de la autocaptura de interacciones del DOM.
|
|
@@ -26,6 +26,11 @@ interface VeoAutocaptureConfig {
|
|
|
26
26
|
captureElementMetadata?: boolean;
|
|
27
27
|
/** Niveles de ancestros a capturar. Default: 5. Max: 10. */
|
|
28
28
|
captureAncestors?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Calidad de los clics: emite `$dead_click` (clic sin reacción), `$rage_click`
|
|
31
|
+
* (ráfaga de clics) y `$click_error` (clic seguido de un error). Default: true.
|
|
32
|
+
*/
|
|
33
|
+
captureClickQuality?: boolean;
|
|
29
34
|
}
|
|
30
35
|
/**
|
|
31
36
|
* Configuración del cliente Veo.
|
|
@@ -49,6 +54,18 @@ interface VeoConfig {
|
|
|
49
54
|
* tooltips. Default: `false`.
|
|
50
55
|
*/
|
|
51
56
|
guides?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Detectar el tema visual de la app (colores base en claro/oscuro y fuentes)
|
|
59
|
+
* para que el design system de OnUser arranque con la marca del cliente.
|
|
60
|
+
* Solo viajan colores y nombres de fuentes, como mucho una vez al día por
|
|
61
|
+
* navegador. Default: `true`; `false` lo deshabilita.
|
|
62
|
+
*/
|
|
63
|
+
detectTheme?: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* URL del bundle del detector de tema (`veo-theme.js`). Solo aplica al build
|
|
66
|
+
* IIFE; si se omite, se deriva del `<script>` que cargó `veo.js`.
|
|
67
|
+
*/
|
|
68
|
+
themeUrl?: string;
|
|
52
69
|
/**
|
|
53
70
|
* URL del bundle del builder (`veo-builder.js`). Solo se carga cuando la
|
|
54
71
|
* página trae `?veoBuilder=<token>` (modo edición desde el dashboard). Si
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { definePlugin, guidesPlugin, runPreviewMode } from './chunk-
|
|
2
|
-
export { definePlugin, guidesPlugin } from './chunk-
|
|
3
|
-
import { hasWindow, hasDocument, uuidv7, DEFAULT_AUTOCAPTURE_CONFIG, MAX_ANCESTORS, hasLocalStorage, hasNavigatorSendBeacon,
|
|
4
|
-
export { closeGuidePreview, previewGuide } from './chunk-
|
|
1
|
+
import { definePlugin, guidesPlugin, runPreviewMode } from './chunk-3JEAPZ6A.mjs';
|
|
2
|
+
export { definePlugin, guidesPlugin } from './chunk-3JEAPZ6A.mjs';
|
|
3
|
+
import { hasWindow, hasDocument, uuidv7, DEFAULT_AUTOCAPTURE_CONFIG, MAX_ANCESTORS, hasLocalStorage, hasNavigatorSendBeacon, isBuilderMode, buildSelectorPath, ALWAYS_BLOCK_SELECTORS, resolveBuilderToken, filterHumanClasses, PRIORITY_ATTRIBUTES, SENSITIVE_ATTRIBUTES, getPreviewToken, previewGuide, closeGuidePreview } from './chunk-SZCIXA5X.mjs';
|
|
4
|
+
export { closeGuidePreview, previewGuide } from './chunk-SZCIXA5X.mjs';
|
|
5
5
|
import './chunk-PTG3BTJE.mjs';
|
|
6
6
|
|
|
7
7
|
// src/contract/v1.ts
|
|
@@ -62,6 +62,294 @@ function deriveBuilderUrl() {
|
|
|
62
62
|
return null;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
// src/plugins/autocapture/click-quality.ts
|
|
66
|
+
var NO_DEAD_TAGS = /* @__PURE__ */ new Set(["input", "textarea", "select", "option", "label"]);
|
|
67
|
+
var MAX_ERROR_MESSAGE = 200;
|
|
68
|
+
var HISTORY_MS = 3e3;
|
|
69
|
+
var DEFAULTS = {
|
|
70
|
+
reactionWindowMs: 1e3,
|
|
71
|
+
errorWindowMs: 2e3,
|
|
72
|
+
rageClicks: 3,
|
|
73
|
+
rageWindowMs: 1e3,
|
|
74
|
+
rageClicksSlow: 5,
|
|
75
|
+
rageWindowSlowMs: 3e3,
|
|
76
|
+
maxPerElement: 50
|
|
77
|
+
};
|
|
78
|
+
function describeError(value) {
|
|
79
|
+
let text;
|
|
80
|
+
if (value instanceof Error) text = `${value.name}: ${value.message}`;
|
|
81
|
+
else if (typeof value === "string") text = value;
|
|
82
|
+
else {
|
|
83
|
+
try {
|
|
84
|
+
text = JSON.stringify(value) ?? String(value);
|
|
85
|
+
} catch {
|
|
86
|
+
text = String(value);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return text.slice(0, MAX_ERROR_MESSAGE);
|
|
90
|
+
}
|
|
91
|
+
var ClickQualityTracker = class {
|
|
92
|
+
constructor(emit, options = {}) {
|
|
93
|
+
this.emit = emit;
|
|
94
|
+
this.installed = false;
|
|
95
|
+
this.cleanups = [];
|
|
96
|
+
// Sensores globales (contadores: cada observación compara su foto inicial).
|
|
97
|
+
this.netCount = 0;
|
|
98
|
+
this.navCount = 0;
|
|
99
|
+
this.focusCount = 0;
|
|
100
|
+
this.unloading = false;
|
|
101
|
+
this.errors = [];
|
|
102
|
+
this.history = /* @__PURE__ */ new Map();
|
|
103
|
+
this.bursts = /* @__PURE__ */ new Map();
|
|
104
|
+
this.emitted = /* @__PURE__ */ new Map();
|
|
105
|
+
this.opts = { ...DEFAULTS, ...options };
|
|
106
|
+
}
|
|
107
|
+
install() {
|
|
108
|
+
if (this.installed || !hasWindow()) return;
|
|
109
|
+
this.installed = true;
|
|
110
|
+
const on = (type, handler, capture = true) => {
|
|
111
|
+
window.addEventListener(type, handler, capture);
|
|
112
|
+
this.cleanups.push(() => window.removeEventListener(type, handler, capture));
|
|
113
|
+
};
|
|
114
|
+
on("error", (e) => {
|
|
115
|
+
const err = e;
|
|
116
|
+
this.pushError("error", err.error ?? err.message ?? "error");
|
|
117
|
+
});
|
|
118
|
+
on("unhandledrejection", (e) => {
|
|
119
|
+
this.pushError("unhandledrejection", e.reason ?? "rejection");
|
|
120
|
+
});
|
|
121
|
+
on("popstate", () => this.navCount++);
|
|
122
|
+
on("hashchange", () => this.navCount++);
|
|
123
|
+
on("focusin", () => this.focusCount++);
|
|
124
|
+
on("pagehide", () => {
|
|
125
|
+
this.unloading = true;
|
|
126
|
+
});
|
|
127
|
+
on("beforeunload", () => {
|
|
128
|
+
this.unloading = true;
|
|
129
|
+
});
|
|
130
|
+
this.patchHistory();
|
|
131
|
+
this.patchConsole();
|
|
132
|
+
this.patchFetch();
|
|
133
|
+
this.patchXhr();
|
|
134
|
+
}
|
|
135
|
+
destroy() {
|
|
136
|
+
for (const c of this.cleanups) c();
|
|
137
|
+
this.cleanups = [];
|
|
138
|
+
for (const b of this.bursts.values()) window.clearTimeout(b.timer);
|
|
139
|
+
this.bursts.clear();
|
|
140
|
+
this.history.clear();
|
|
141
|
+
this.installed = false;
|
|
142
|
+
}
|
|
143
|
+
/** Llamar por cada clic real sobre un elemento interactivo, ANTES del throttle. */
|
|
144
|
+
onClick(element, event) {
|
|
145
|
+
if (!this.installed) return;
|
|
146
|
+
const now = Date.now();
|
|
147
|
+
const obs = { at: now, reacted: false };
|
|
148
|
+
this.remember(element, obs);
|
|
149
|
+
this.observeReaction(element, obs, event);
|
|
150
|
+
this.observeErrors(element, now);
|
|
151
|
+
this.trackRage(element, now);
|
|
152
|
+
}
|
|
153
|
+
// ---------------------------------------------------------------- dead ----
|
|
154
|
+
observeReaction(element, obs, event) {
|
|
155
|
+
const netAtClick = this.netCount;
|
|
156
|
+
const navAtClick = this.navCount;
|
|
157
|
+
const focusAtClick = this.focusCount;
|
|
158
|
+
const hrefAtClick = window.location.href;
|
|
159
|
+
const skipDead = this.isDeadExempt(element, event);
|
|
160
|
+
let observer = null;
|
|
161
|
+
if (typeof MutationObserver !== "undefined") {
|
|
162
|
+
observer = new MutationObserver(() => {
|
|
163
|
+
obs.reacted = true;
|
|
164
|
+
observer?.disconnect();
|
|
165
|
+
observer = null;
|
|
166
|
+
});
|
|
167
|
+
observer.observe(document.documentElement, {
|
|
168
|
+
childList: true,
|
|
169
|
+
attributes: true,
|
|
170
|
+
characterData: true,
|
|
171
|
+
subtree: true
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
window.setTimeout(() => {
|
|
175
|
+
observer?.disconnect();
|
|
176
|
+
const reacted = obs.reacted || this.unloading || this.netCount !== netAtClick || this.navCount !== navAtClick || this.focusCount !== focusAtClick || window.location.href !== hrefAtClick;
|
|
177
|
+
obs.reacted = reacted;
|
|
178
|
+
if (!reacted && !skipDead) this.report("$dead_click", element, {});
|
|
179
|
+
}, this.opts.reactionWindowMs);
|
|
180
|
+
}
|
|
181
|
+
isDeadExempt(element, event) {
|
|
182
|
+
if (event && (event.ctrlKey || event.metaKey || event.shiftKey || event.altKey)) return true;
|
|
183
|
+
if (event && typeof event.button === "number" && event.button !== 0) return true;
|
|
184
|
+
const tag = element.tagName.toLowerCase();
|
|
185
|
+
if (NO_DEAD_TAGS.has(tag)) return true;
|
|
186
|
+
if (element.closest('[contenteditable=""],[contenteditable="true"]')) return true;
|
|
187
|
+
const anchor = element.closest("a");
|
|
188
|
+
if (anchor) {
|
|
189
|
+
if (anchor.target === "_blank" || anchor.hasAttribute("download")) return true;
|
|
190
|
+
const href = anchor.getAttribute("href") ?? "";
|
|
191
|
+
if (/^(mailto|tel|sms):/i.test(href)) return true;
|
|
192
|
+
}
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
// --------------------------------------------------------------- error ----
|
|
196
|
+
observeErrors(element, clickedAt) {
|
|
197
|
+
const deadline = clickedAt + this.opts.errorWindowMs;
|
|
198
|
+
const check = () => {
|
|
199
|
+
const err = this.errors.find((e) => e.at >= clickedAt && e.at <= deadline);
|
|
200
|
+
if (err) {
|
|
201
|
+
this.report("$click_error", element, {
|
|
202
|
+
$error_source: err.source,
|
|
203
|
+
$error_message: err.message
|
|
204
|
+
});
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (Date.now() < deadline) window.setTimeout(check, 250);
|
|
208
|
+
};
|
|
209
|
+
window.setTimeout(check, 250);
|
|
210
|
+
}
|
|
211
|
+
pushError(source, value) {
|
|
212
|
+
const message = describeError(value);
|
|
213
|
+
if (message.startsWith("[veo]")) return;
|
|
214
|
+
this.errors.push({ at: Date.now(), source, message });
|
|
215
|
+
const cutoff = Date.now() - this.opts.errorWindowMs - 1e3;
|
|
216
|
+
while (this.errors.length > 0 && this.errors[0].at < cutoff) {
|
|
217
|
+
this.errors.shift();
|
|
218
|
+
}
|
|
219
|
+
if (this.errors.length > 50) this.errors.splice(0, this.errors.length - 50);
|
|
220
|
+
}
|
|
221
|
+
// ---------------------------------------------------------------- rage ----
|
|
222
|
+
remember(element, obs) {
|
|
223
|
+
const list = this.history.get(element) ?? [];
|
|
224
|
+
const cutoff = obs.at - HISTORY_MS;
|
|
225
|
+
const kept = list.filter((o) => o.at >= cutoff);
|
|
226
|
+
kept.push(obs);
|
|
227
|
+
this.history.set(element, kept);
|
|
228
|
+
if (this.history.size > 100) {
|
|
229
|
+
for (const [el, obsList] of this.history) {
|
|
230
|
+
if (obsList.every((o) => o.at < cutoff)) this.history.delete(el);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
trackRage(element, now) {
|
|
235
|
+
const list = this.history.get(element) ?? [];
|
|
236
|
+
const fast = list.filter((o) => o.at >= now - this.opts.rageWindowMs).length;
|
|
237
|
+
const slow = list.filter((o) => o.at >= now - this.opts.rageWindowSlowMs).length;
|
|
238
|
+
const existing = this.bursts.get(element);
|
|
239
|
+
const isRage = fast >= this.opts.rageClicks || slow >= this.opts.rageClicksSlow;
|
|
240
|
+
if (!existing && !isRage) return;
|
|
241
|
+
const burst = existing ?? {
|
|
242
|
+
startedAt: (list.find((o) => o.at >= now - this.opts.rageWindowSlowMs) ?? { at: now }).at,
|
|
243
|
+
count: 0,
|
|
244
|
+
timer: 0
|
|
245
|
+
};
|
|
246
|
+
burst.count = list.filter((o) => o.at >= burst.startedAt).length;
|
|
247
|
+
window.clearTimeout(burst.timer);
|
|
248
|
+
burst.timer = window.setTimeout(() => {
|
|
249
|
+
this.bursts.delete(element);
|
|
250
|
+
const clicks = (this.history.get(element) ?? []).filter((o) => o.at >= burst.startedAt);
|
|
251
|
+
this.report("$rage_click", element, {
|
|
252
|
+
$click_count: Math.max(clicks.length, burst.count),
|
|
253
|
+
$reacted_clicks: clicks.filter((o) => o.reacted).length
|
|
254
|
+
});
|
|
255
|
+
}, this.opts.reactionWindowMs + 50);
|
|
256
|
+
this.bursts.set(element, burst);
|
|
257
|
+
}
|
|
258
|
+
// ---------------------------------------------------------------- emit ----
|
|
259
|
+
report(kind, element, extra) {
|
|
260
|
+
const key = buildSelectorPath(element, 5);
|
|
261
|
+
const counts = this.emitted.get(key) ?? { $dead_click: 0, $rage_click: 0, $click_error: 0 };
|
|
262
|
+
if (counts[kind] >= this.opts.maxPerElement) return;
|
|
263
|
+
counts[kind]++;
|
|
264
|
+
this.emitted.set(key, counts);
|
|
265
|
+
try {
|
|
266
|
+
this.emit(kind, element, extra);
|
|
267
|
+
} catch {
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// ------------------------------------------------------------- patches ----
|
|
271
|
+
patchHistory() {
|
|
272
|
+
const h = window.history;
|
|
273
|
+
if (!h) return;
|
|
274
|
+
const origPush = h.pushState;
|
|
275
|
+
const origReplace = h.replaceState;
|
|
276
|
+
const self = this;
|
|
277
|
+
h.pushState = function(...args) {
|
|
278
|
+
self.navCount++;
|
|
279
|
+
return origPush.apply(this, args);
|
|
280
|
+
};
|
|
281
|
+
h.replaceState = function(...args) {
|
|
282
|
+
self.navCount++;
|
|
283
|
+
return origReplace.apply(this, args);
|
|
284
|
+
};
|
|
285
|
+
this.cleanups.push(() => {
|
|
286
|
+
h.pushState = origPush;
|
|
287
|
+
h.replaceState = origReplace;
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
patchConsole() {
|
|
291
|
+
const orig = console.error;
|
|
292
|
+
const self = this;
|
|
293
|
+
console.error = function(...args) {
|
|
294
|
+
try {
|
|
295
|
+
self.pushError("console", args.map(describeError).join(" "));
|
|
296
|
+
} catch {
|
|
297
|
+
}
|
|
298
|
+
return orig.apply(this, args);
|
|
299
|
+
};
|
|
300
|
+
this.cleanups.push(() => {
|
|
301
|
+
console.error = orig;
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* `fetch`: cuenta la petición (reacción) y registra respuestas ≥500 o
|
|
306
|
+
* rechazos como error. Se observa con `.then(ok, err)` sobre la promesa
|
|
307
|
+
* original: eso marca el rechazo como manejado para el navegador, así que
|
|
308
|
+
* un fetch fallido sin catch de la app no dispara `unhandledrejection` —
|
|
309
|
+
* lo registramos nosotros como error de red.
|
|
310
|
+
*/
|
|
311
|
+
patchFetch() {
|
|
312
|
+
if (typeof window.fetch !== "function") return;
|
|
313
|
+
const orig = window.fetch;
|
|
314
|
+
const self = this;
|
|
315
|
+
window.fetch = function(...args) {
|
|
316
|
+
self.netCount++;
|
|
317
|
+
const p = orig.apply(this, args);
|
|
318
|
+
if (p && typeof p.then === "function") {
|
|
319
|
+
p.then(
|
|
320
|
+
(res) => {
|
|
321
|
+
if (res && res.status >= 500) self.pushError("fetch", `${res.status} ${res.url}`);
|
|
322
|
+
},
|
|
323
|
+
(err) => self.pushError("fetch", err)
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
return p;
|
|
327
|
+
};
|
|
328
|
+
this.cleanups.push(() => {
|
|
329
|
+
window.fetch = orig;
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
patchXhr() {
|
|
333
|
+
const proto = typeof XMLHttpRequest !== "undefined" ? XMLHttpRequest.prototype : null;
|
|
334
|
+
if (!proto) return;
|
|
335
|
+
const origSend = proto.send;
|
|
336
|
+
const self = this;
|
|
337
|
+
proto.send = function(...args) {
|
|
338
|
+
self.netCount++;
|
|
339
|
+
try {
|
|
340
|
+
this.addEventListener("loadend", () => {
|
|
341
|
+
if (this.status >= 500) self.pushError("xhr", `${this.status} ${this.responseURL}`);
|
|
342
|
+
});
|
|
343
|
+
} catch {
|
|
344
|
+
}
|
|
345
|
+
return origSend.apply(this, args);
|
|
346
|
+
};
|
|
347
|
+
this.cleanups.push(() => {
|
|
348
|
+
proto.send = origSend;
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
|
|
65
353
|
// src/plugins/autocapture/pii-guard.ts
|
|
66
354
|
function isElementBlocked(element, extraBlockSelectors = []) {
|
|
67
355
|
const allBlockers = [...ALWAYS_BLOCK_SELECTORS, ...extraBlockSelectors];
|
|
@@ -165,6 +453,7 @@ var DomListener = class {
|
|
|
165
453
|
constructor(client, config) {
|
|
166
454
|
this.client = client;
|
|
167
455
|
this.cleanups = [];
|
|
456
|
+
this.quality = null;
|
|
168
457
|
const d = DEFAULT_AUTOCAPTURE_CONFIG;
|
|
169
458
|
this.config = {
|
|
170
459
|
enabled: config.enabled ?? d.enabled,
|
|
@@ -177,12 +466,20 @@ var DomListener = class {
|
|
|
177
466
|
throttleMs: config.throttleMs ?? d.throttleMs,
|
|
178
467
|
maxTextLength: config.maxTextLength ?? d.maxTextLength,
|
|
179
468
|
captureElementMetadata: config.captureElementMetadata ?? d.captureElementMetadata,
|
|
180
|
-
captureAncestors: Math.min(config.captureAncestors ?? d.captureAncestors, MAX_ANCESTORS)
|
|
469
|
+
captureAncestors: Math.min(config.captureAncestors ?? d.captureAncestors, MAX_ANCESTORS),
|
|
470
|
+
captureClickQuality: config.captureClickQuality ?? d.captureClickQuality
|
|
181
471
|
};
|
|
182
472
|
this.throttle = new ElementThrottle(this.config.throttleMs);
|
|
183
473
|
}
|
|
184
474
|
install() {
|
|
185
475
|
if (!hasDocument() || !this.config.enabled) return;
|
|
476
|
+
if (this.config.captureClicks && this.config.captureClickQuality) {
|
|
477
|
+
this.quality = new ClickQualityTracker((kind, element, extra) => {
|
|
478
|
+
const payload = buildAutocapturePayload("click", element, this.config);
|
|
479
|
+
this.client.track(kind, { ...payload, ...extra });
|
|
480
|
+
});
|
|
481
|
+
this.quality.install();
|
|
482
|
+
}
|
|
186
483
|
if (this.config.captureClicks) this.attach("click", (e) => this.handleClick(e));
|
|
187
484
|
if (this.config.captureSubmits) this.attach("submit", (e) => this.handleSubmit(e), true);
|
|
188
485
|
if (this.config.captureChanges) this.attach("change", (e) => this.handleChange(e), true);
|
|
@@ -190,6 +487,8 @@ var DomListener = class {
|
|
|
190
487
|
destroy() {
|
|
191
488
|
for (const cleanup of this.cleanups) cleanup();
|
|
192
489
|
this.cleanups = [];
|
|
490
|
+
this.quality?.destroy();
|
|
491
|
+
this.quality = null;
|
|
193
492
|
}
|
|
194
493
|
attach(eventName, handler, useCapture = false) {
|
|
195
494
|
const listener = (e) => {
|
|
@@ -208,6 +507,7 @@ var DomListener = class {
|
|
|
208
507
|
const meaningful = this.findMeaningfulAncestor(target);
|
|
209
508
|
if (!meaningful) return;
|
|
210
509
|
if (isElementBlocked(meaningful, this.config.blockSelectors)) return;
|
|
510
|
+
this.quality?.onClick(meaningful, event);
|
|
211
511
|
if (!this.throttle.shouldFire(meaningful)) return;
|
|
212
512
|
this.emit("click", meaningful);
|
|
213
513
|
}
|
|
@@ -291,6 +591,118 @@ function spaRouterPlugin() {
|
|
|
291
591
|
});
|
|
292
592
|
}
|
|
293
593
|
|
|
594
|
+
// src/plugins/theme-detect/index.ts
|
|
595
|
+
var STORAGE_PREFIX = "veo_theme_";
|
|
596
|
+
var RESEND_AFTER_MS = 24 * 60 * 60 * 1e3;
|
|
597
|
+
var SETTLE_MS = 4e3;
|
|
598
|
+
var MAX_RUNS_PER_PAGE = 4;
|
|
599
|
+
function readState(key) {
|
|
600
|
+
try {
|
|
601
|
+
const raw = localStorage.getItem(key);
|
|
602
|
+
const parsed = raw ? JSON.parse(raw) : null;
|
|
603
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
604
|
+
} catch {
|
|
605
|
+
return {};
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
function writeState(key, state) {
|
|
609
|
+
try {
|
|
610
|
+
localStorage.setItem(key, JSON.stringify(state));
|
|
611
|
+
} catch {
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
function guessMode(doc, prefersDark) {
|
|
615
|
+
const hints = [
|
|
616
|
+
doc.documentElement.className,
|
|
617
|
+
doc.documentElement.dataset.theme,
|
|
618
|
+
doc.documentElement.dataset.mode,
|
|
619
|
+
doc.body?.className
|
|
620
|
+
].join(" ").toLowerCase();
|
|
621
|
+
if (/\bdark\b/.test(hints)) return "dark";
|
|
622
|
+
if (/\blight\b/.test(hints)) return "light";
|
|
623
|
+
return prefersDark ? "dark" : "light";
|
|
624
|
+
}
|
|
625
|
+
function isFresh(state, mode, now) {
|
|
626
|
+
const last = state[mode];
|
|
627
|
+
return last !== void 0 && now - last.at < RESEND_AFTER_MS;
|
|
628
|
+
}
|
|
629
|
+
function shouldSendSnapshot(state, mode, hash, now) {
|
|
630
|
+
const last = state[mode];
|
|
631
|
+
return !last || last.hash !== hash || now - last.at >= RESEND_AFTER_MS;
|
|
632
|
+
}
|
|
633
|
+
function themeDetectPlugin(config) {
|
|
634
|
+
const storageKey = `${STORAGE_PREFIX}${config.apiKey.slice(-16) || "default"}`;
|
|
635
|
+
let observer = null;
|
|
636
|
+
let timer = null;
|
|
637
|
+
let destroyed = false;
|
|
638
|
+
let runs = 0;
|
|
639
|
+
const run = async () => {
|
|
640
|
+
if (destroyed || runs >= MAX_RUNS_PER_PAGE) return;
|
|
641
|
+
const prefersDark = typeof window.matchMedia === "function" && window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
642
|
+
const guess = guessMode(document, prefersDark);
|
|
643
|
+
if (isFresh(readState(storageKey), guess, Date.now())) return;
|
|
644
|
+
runs++;
|
|
645
|
+
try {
|
|
646
|
+
const detector = await config.loadDetector();
|
|
647
|
+
if (!detector || destroyed) return;
|
|
648
|
+
const snapshot = detector.detectTheme();
|
|
649
|
+
if (!snapshot) return;
|
|
650
|
+
const hash = detector.snapshotHash(snapshot);
|
|
651
|
+
const state = readState(storageKey);
|
|
652
|
+
const now = Date.now();
|
|
653
|
+
const mark = () => {
|
|
654
|
+
const record = { hash, at: Date.now() };
|
|
655
|
+
state[snapshot.mode] = record;
|
|
656
|
+
state[guess] = state[guess] && guess !== snapshot.mode ? state[guess] : record;
|
|
657
|
+
writeState(storageKey, state);
|
|
658
|
+
};
|
|
659
|
+
if (!shouldSendSnapshot(state, snapshot.mode, hash, now)) {
|
|
660
|
+
mark();
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
if (config.debug) console.log("[veo] theme snapshot", snapshot);
|
|
664
|
+
const res = await fetch(`${config.apiUrl}/v1/workspaces/theme-snapshot`, {
|
|
665
|
+
method: "POST",
|
|
666
|
+
headers: { "X-Api-Key": config.apiKey, "Content-Type": "application/json" },
|
|
667
|
+
body: JSON.stringify(snapshot),
|
|
668
|
+
credentials: "omit"
|
|
669
|
+
});
|
|
670
|
+
if (res.ok) mark();
|
|
671
|
+
} catch (err) {
|
|
672
|
+
if (config.debug) console.warn("[veo] theme detect failed", err);
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
const schedule = (delayMs) => {
|
|
676
|
+
if (timer) clearTimeout(timer);
|
|
677
|
+
timer = setTimeout(() => {
|
|
678
|
+
timer = null;
|
|
679
|
+
const idle = window.requestIdleCallback;
|
|
680
|
+
if (typeof idle === "function") idle(() => void run());
|
|
681
|
+
else void run();
|
|
682
|
+
}, delayMs);
|
|
683
|
+
};
|
|
684
|
+
return {
|
|
685
|
+
name: "theme-detect",
|
|
686
|
+
install() {
|
|
687
|
+
if (!hasWindow() || !hasDocument()) return;
|
|
688
|
+
if (document.readyState === "complete") schedule(SETTLE_MS);
|
|
689
|
+
else window.addEventListener("load", () => schedule(SETTLE_MS), { once: true });
|
|
690
|
+
if (typeof MutationObserver === "function") {
|
|
691
|
+
observer = new MutationObserver(() => schedule(1500));
|
|
692
|
+
observer.observe(document.documentElement, {
|
|
693
|
+
attributes: true,
|
|
694
|
+
attributeFilter: ["class", "data-theme", "data-mode"]
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
},
|
|
698
|
+
destroy() {
|
|
699
|
+
destroyed = true;
|
|
700
|
+
observer?.disconnect();
|
|
701
|
+
if (timer) clearTimeout(timer);
|
|
702
|
+
}
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
|
|
294
706
|
// src/core/queue.ts
|
|
295
707
|
var RETRY_MAX_DELAY_MS = 3e4;
|
|
296
708
|
var Queue = class {
|
|
@@ -437,26 +849,26 @@ var Session = class {
|
|
|
437
849
|
};
|
|
438
850
|
|
|
439
851
|
// src/core/storage.ts
|
|
440
|
-
var
|
|
852
|
+
var STORAGE_PREFIX2 = "veo:";
|
|
441
853
|
var ONE_YEAR_MS = 365 * 24 * 60 * 60 * 1e3;
|
|
442
854
|
var LocalStorageAdapter = class {
|
|
443
855
|
get(key) {
|
|
444
|
-
return localStorage.getItem(
|
|
856
|
+
return localStorage.getItem(STORAGE_PREFIX2 + key);
|
|
445
857
|
}
|
|
446
858
|
set(key, value) {
|
|
447
859
|
try {
|
|
448
|
-
localStorage.setItem(
|
|
860
|
+
localStorage.setItem(STORAGE_PREFIX2 + key, value);
|
|
449
861
|
} catch {
|
|
450
862
|
}
|
|
451
863
|
}
|
|
452
864
|
remove(key) {
|
|
453
|
-
localStorage.removeItem(
|
|
865
|
+
localStorage.removeItem(STORAGE_PREFIX2 + key);
|
|
454
866
|
}
|
|
455
867
|
clear() {
|
|
456
868
|
const toRemove = [];
|
|
457
869
|
for (let i = 0; i < localStorage.length; i++) {
|
|
458
870
|
const key = localStorage.key(i);
|
|
459
|
-
if (key?.startsWith(
|
|
871
|
+
if (key?.startsWith(STORAGE_PREFIX2)) toRemove.push(key);
|
|
460
872
|
}
|
|
461
873
|
for (const key of toRemove) localStorage.removeItem(key);
|
|
462
874
|
}
|
|
@@ -467,7 +879,7 @@ function readCookies() {
|
|
|
467
879
|
}
|
|
468
880
|
var CookieAdapter = class {
|
|
469
881
|
get(key) {
|
|
470
|
-
const name = `${
|
|
882
|
+
const name = `${STORAGE_PREFIX2}${key}=`;
|
|
471
883
|
for (const cookie of readCookies()) {
|
|
472
884
|
if (cookie.startsWith(name)) {
|
|
473
885
|
return decodeURIComponent(cookie.slice(name.length));
|
|
@@ -478,19 +890,19 @@ var CookieAdapter = class {
|
|
|
478
890
|
set(key, value, options) {
|
|
479
891
|
const maxAgeSec = Math.floor((options?.ttlMs ?? ONE_YEAR_MS) / 1e3);
|
|
480
892
|
const secure = typeof location !== "undefined" && location.protocol === "https:" ? "; Secure" : "";
|
|
481
|
-
document.cookie = `${
|
|
893
|
+
document.cookie = `${STORAGE_PREFIX2}${key}=${encodeURIComponent(
|
|
482
894
|
value
|
|
483
895
|
)}; Path=/; Max-Age=${maxAgeSec}; SameSite=Lax${secure}`;
|
|
484
896
|
}
|
|
485
897
|
remove(key) {
|
|
486
|
-
document.cookie = `${
|
|
898
|
+
document.cookie = `${STORAGE_PREFIX2}${key}=; Path=/; Max-Age=0; SameSite=Lax`;
|
|
487
899
|
}
|
|
488
900
|
clear() {
|
|
489
901
|
for (const cookie of readCookies()) {
|
|
490
902
|
const eq = cookie.indexOf("=");
|
|
491
903
|
const name = eq === -1 ? cookie : cookie.slice(0, eq);
|
|
492
|
-
if (name.startsWith(
|
|
493
|
-
this.remove(name.slice(
|
|
904
|
+
if (name.startsWith(STORAGE_PREFIX2)) {
|
|
905
|
+
this.remove(name.slice(STORAGE_PREFIX2.length));
|
|
494
906
|
}
|
|
495
907
|
}
|
|
496
908
|
}
|
|
@@ -779,13 +1191,23 @@ var Client = class {
|
|
|
779
1191
|
};
|
|
780
1192
|
|
|
781
1193
|
// src/core/setup.ts
|
|
782
|
-
function setupClient(config, onGuides, loadBuilder) {
|
|
1194
|
+
function setupClient(config, onGuides, loadBuilder, loadThemeDetector) {
|
|
783
1195
|
const client = new Client(config);
|
|
784
1196
|
client.use(spaRouterPlugin());
|
|
785
1197
|
if (config.autocapture !== false) {
|
|
786
1198
|
const autocaptureConfig = typeof config.autocapture === "object" ? config.autocapture : {};
|
|
787
1199
|
client.use(autocapturePlugin(autocaptureConfig));
|
|
788
1200
|
}
|
|
1201
|
+
if (config.detectTheme !== false && loadThemeDetector && !isBuilderMode()) {
|
|
1202
|
+
client.use(
|
|
1203
|
+
themeDetectPlugin({
|
|
1204
|
+
apiUrl: resolveApiUrl(config),
|
|
1205
|
+
apiKey: config.apiKey,
|
|
1206
|
+
loadDetector: loadThemeDetector,
|
|
1207
|
+
...config.debug !== void 0 ? { debug: config.debug } : {}
|
|
1208
|
+
})
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
789
1211
|
onGuides(client);
|
|
790
1212
|
if (typeof window !== "undefined") {
|
|
791
1213
|
const stub = window.veo;
|
|
@@ -844,7 +1266,10 @@ function init(config) {
|
|
|
844
1266
|
// Fallback de carga del builder para bundlers: `import()` dinámico → el bundler
|
|
845
1267
|
// lo code-splitea (solo se descarga en modo builder). Así el consumidor por
|
|
846
1268
|
// bundler NO necesita copiar `veo-builder.js` ni setear `builderUrl`.
|
|
847
|
-
() => import('./builder-
|
|
1269
|
+
() => import('./builder-PPLIAUCM.mjs').then((m) => m.initBuilderMode()),
|
|
1270
|
+
// Detector de tema: también por `import()` dinámico → chunk aparte que el
|
|
1271
|
+
// bundler solo descarga cuando toca detectar (como mucho una vez al día).
|
|
1272
|
+
() => import('./detect-theme-X4K3TLUL.mjs')
|
|
848
1273
|
);
|
|
849
1274
|
}
|
|
850
1275
|
|