veo-sdk 0.1.0 → 0.2.1
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 +17 -11
- package/dist/builder.cjs +2072 -0
- package/dist/builder.cjs.map +1 -0
- package/dist/builder.d.cts +162 -0
- package/dist/builder.d.ts +162 -0
- package/dist/builder.mjs +449 -0
- package/dist/builder.mjs.map +1 -0
- package/dist/chunk-BBAA5BRS.mjs +2728 -0
- package/dist/chunk-BBAA5BRS.mjs.map +1 -0
- package/dist/guide-preview-CO5ShtNf.d.cts +137 -0
- package/dist/guide-preview-CO5ShtNf.d.ts +137 -0
- package/dist/index.cjs +1785 -727
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +43 -28
- package/dist/index.d.ts +43 -28
- package/dist/index.mjs +343 -2009
- package/dist/index.mjs.map +1 -1
- package/dist/veo-builder.js +289 -0
- package/dist/veo-builder.js.map +1 -0
- package/dist/veo-guides.js +259 -0
- package/dist/veo-guides.js.map +1 -0
- package/dist/veo.js +2 -169
- package/dist/veo.js.map +1 -1
- package/package.json +7 -2
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { P as PreviewGuideInput } from './guide-preview-CO5ShtNf.js';
|
|
2
|
+
export { a as PreviewHandle, b as PreviewResult, c as closeGuidePreview, p as previewGuide } from './guide-preview-CO5ShtNf.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Element picker — el "inspeccionar" del navegador, pero del SDK. Corre
|
|
6
|
+
* DENTRO de la app del cliente (no en el dashboard: son orígenes distintos
|
|
7
|
+
* y el dashboard no puede leer el DOM de la app). Resalta el elemento bajo
|
|
8
|
+
* el cursor, muestra su selector + dimensiones, y al hacer click captura un
|
|
9
|
+
* selector CSS estable reutilizando `buildSelectorPath` del autocapture.
|
|
10
|
+
*
|
|
11
|
+
* Es la base del builder no-code: el dashboard abre la app del cliente en
|
|
12
|
+
* modo builder, el usuario "pincha" un elemento y el selector resultante se
|
|
13
|
+
* usa como ancla de un tooltip/walkthrough. El puente con el dashboard
|
|
14
|
+
* (postMessage) es la fase siguiente; aquí exponemos el primitivo
|
|
15
|
+
* `startElementPicker(onPick)`.
|
|
16
|
+
*
|
|
17
|
+
* No usa `innerHTML` — todo por `textContent`/DOM, igual que los renderers.
|
|
18
|
+
*/
|
|
19
|
+
/** Datos del elemento elegido que se entregan al caller. */
|
|
20
|
+
interface PickedElement {
|
|
21
|
+
/** Selector CSS estable con ancestros (lo que se guarda como ancla). */
|
|
22
|
+
selector: string;
|
|
23
|
+
/** Selector corto solo del elemento (tag + id/atributo/clases). */
|
|
24
|
+
shortSelector: string;
|
|
25
|
+
tag: string;
|
|
26
|
+
id: string | null;
|
|
27
|
+
classes: string[];
|
|
28
|
+
text: string;
|
|
29
|
+
rect: {
|
|
30
|
+
x: number;
|
|
31
|
+
y: number;
|
|
32
|
+
width: number;
|
|
33
|
+
height: number;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
interface ElementPickerOptions {
|
|
37
|
+
/** Llamado al hacer click en un elemento. El picker se cierra tras esto. */
|
|
38
|
+
onPick: (picked: PickedElement) => void;
|
|
39
|
+
/** Llamado al cancelar (Esc). El picker se cierra. */
|
|
40
|
+
onCancel?: () => void;
|
|
41
|
+
/** Niveles de ancestros del selector. Default 5 (igual que autocapture). */
|
|
42
|
+
maxAncestors?: number;
|
|
43
|
+
/** Texto del banner superior. Default en español. */
|
|
44
|
+
hint?: string;
|
|
45
|
+
}
|
|
46
|
+
interface ElementPickerHandle {
|
|
47
|
+
/** Cierra el picker sin elegir nada. Idempotente. */
|
|
48
|
+
stop(): void;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Inicia el modo de selección de elementos (inspector). Resalta lo que está
|
|
52
|
+
* bajo el cursor y, al hacer click, entrega un selector CSS estable.
|
|
53
|
+
* Devuelve un handle con `stop()`. Iniciar otro picker cierra el anterior.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* import { startElementPicker } from '@veo/sdk';
|
|
58
|
+
*
|
|
59
|
+
* const picker = startElementPicker({
|
|
60
|
+
* onPick: ({ selector }) => console.log('elegido:', selector),
|
|
61
|
+
* onCancel: () => console.log('cancelado'),
|
|
62
|
+
* });
|
|
63
|
+
* // picker.stop() para abortar manualmente.
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
declare function startElementPicker(options: ElementPickerOptions): ElementPickerHandle;
|
|
67
|
+
/** Cierra el picker activo, si lo hay. Idempotente. */
|
|
68
|
+
declare function stopElementPicker(): void;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Contrato de mensajes del puente builder (app del cliente ↔ dashboard).
|
|
72
|
+
* Ambos lados validan `event.origin` y que `token` coincida antes de actuar.
|
|
73
|
+
*
|
|
74
|
+
* Flujo:
|
|
75
|
+
* 1. El dashboard abre la app con `?veoBuilder=<token>&veoBuilderOrigin=<origen>`.
|
|
76
|
+
* 2. La app (veo-builder.js) postea `ready` a la ventana que la abrió.
|
|
77
|
+
* 3. El dashboard manda comandos (`start-picker`, `preview`, …).
|
|
78
|
+
* 4. La app responde con eventos (`picked`, `pick-cancelled`, …).
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
/** Eventos que la app del cliente envía al dashboard. */
|
|
82
|
+
type BuilderEvent = {
|
|
83
|
+
type: 'ready';
|
|
84
|
+
} | {
|
|
85
|
+
type: 'picked';
|
|
86
|
+
payload: PickedElement;
|
|
87
|
+
} | {
|
|
88
|
+
type: 'pick-cancelled';
|
|
89
|
+
} | {
|
|
90
|
+
type: 'closed';
|
|
91
|
+
};
|
|
92
|
+
/** Comandos que el dashboard envía a la app del cliente. */
|
|
93
|
+
type BuilderCommand = {
|
|
94
|
+
type: 'start-picker';
|
|
95
|
+
maxAncestors?: number;
|
|
96
|
+
hint?: string;
|
|
97
|
+
} | {
|
|
98
|
+
type: 'stop-picker';
|
|
99
|
+
} | {
|
|
100
|
+
type: 'preview';
|
|
101
|
+
guide: PreviewGuideInput;
|
|
102
|
+
} | {
|
|
103
|
+
type: 'close-preview';
|
|
104
|
+
} | {
|
|
105
|
+
type: 'teardown';
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
interface BuilderModeHandle {
|
|
109
|
+
/** Desconecta el puente y limpia picker/preview. */
|
|
110
|
+
teardown(): void;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Inicializa el modo builder si la URL trae el token. Devuelve un handle, o
|
|
114
|
+
* `null` si no estamos en modo builder (uso normal de la app) o no hay un
|
|
115
|
+
* canal con el dashboard. Es seguro llamarla siempre.
|
|
116
|
+
*/
|
|
117
|
+
declare function initBuilderMode(): BuilderModeHandle | null;
|
|
118
|
+
|
|
119
|
+
interface BuilderSessionOptions {
|
|
120
|
+
/** URL de la app del cliente a abrir en modo builder. */
|
|
121
|
+
appUrl: string;
|
|
122
|
+
/** Token de la sesión (idealmente emitido/firmado por el backend). */
|
|
123
|
+
token: string;
|
|
124
|
+
/** Origen al que la app debe responder. Default: `location.origin`. */
|
|
125
|
+
dashboardOrigin?: string;
|
|
126
|
+
/** La app está lista y escuchando comandos. */
|
|
127
|
+
onReady?: () => void;
|
|
128
|
+
/** El usuario eligió un elemento. */
|
|
129
|
+
onPicked?: (picked: PickedElement) => void;
|
|
130
|
+
/** El usuario canceló la selección (Esc). */
|
|
131
|
+
onCancelled?: () => void;
|
|
132
|
+
/** La app cerró el modo builder (o el popup se cerró). */
|
|
133
|
+
onClosed?: () => void;
|
|
134
|
+
/**
|
|
135
|
+
* Si se pasa, la app se embebe en este `<iframe>` (modo estudio in-app) en
|
|
136
|
+
* lugar de abrirse en un popup. El lado app ya habla con `window.parent`,
|
|
137
|
+
* así que el puente funciona igual. La sesión setea el `src` del iframe.
|
|
138
|
+
*/
|
|
139
|
+
frame?: HTMLIFrameElement;
|
|
140
|
+
/** Nombre de la ventana popup. Default: `veo-builder`. */
|
|
141
|
+
windowName?: string;
|
|
142
|
+
/** Features del popup. Default: ventana de ~1280×860. */
|
|
143
|
+
windowFeatures?: string;
|
|
144
|
+
}
|
|
145
|
+
interface BuilderSession {
|
|
146
|
+
/** Pide a la app que entre en modo selección de elemento. */
|
|
147
|
+
startPicker(options?: {
|
|
148
|
+
maxAncestors?: number;
|
|
149
|
+
hint?: string;
|
|
150
|
+
}): void;
|
|
151
|
+
/** Cancela la selección en curso. */
|
|
152
|
+
stopPicker(): void;
|
|
153
|
+
/** Pide a la app que previsualice una guía. */
|
|
154
|
+
preview(guide: PreviewGuideInput): void;
|
|
155
|
+
/** Cierra la preview activa en la app. */
|
|
156
|
+
closePreview(): void;
|
|
157
|
+
/** Cierra el popup y desconecta el puente. */
|
|
158
|
+
close(): void;
|
|
159
|
+
}
|
|
160
|
+
declare function createBuilderSession(options: BuilderSessionOptions): BuilderSession;
|
|
161
|
+
|
|
162
|
+
export { type BuilderCommand, type BuilderEvent, type BuilderModeHandle, type BuilderSession, type BuilderSessionOptions, type ElementPickerHandle, type ElementPickerOptions, type PickedElement, PreviewGuideInput, createBuilderSession, initBuilderMode, startElementPicker, stopElementPicker };
|
package/dist/builder.mjs
ADDED
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
import { hasWindow, hasDocument, closeGuidePreview, previewGuide, buildSelectorPath, filterHumanClasses } from './chunk-BBAA5BRS.mjs';
|
|
2
|
+
export { closeGuidePreview, previewGuide } from './chunk-BBAA5BRS.mjs';
|
|
3
|
+
|
|
4
|
+
// src/plugins/builder/bridge-protocol.ts
|
|
5
|
+
var VEO_BUILDER_SOURCE = "veo-builder";
|
|
6
|
+
var BUILDER_TOKEN_PARAM = "veoBuilder";
|
|
7
|
+
var BUILDER_ORIGIN_PARAM = "veoBuilderOrigin";
|
|
8
|
+
|
|
9
|
+
// src/plugins/builder/element-picker.ts
|
|
10
|
+
var PICKER_Z = 2147483646;
|
|
11
|
+
var PICKER_HOST_ATTR = "data-veo-picker";
|
|
12
|
+
var DEFAULT_MAX_ANCESTORS = 5;
|
|
13
|
+
var PICKER_STYLES = `
|
|
14
|
+
:host { all: initial; }
|
|
15
|
+
.veo-pick-box {
|
|
16
|
+
position: fixed;
|
|
17
|
+
z-index: ${PICKER_Z};
|
|
18
|
+
pointer-events: none;
|
|
19
|
+
box-sizing: border-box;
|
|
20
|
+
background: rgba(99, 102, 241, 0.22);
|
|
21
|
+
border: 1px dashed rgba(99, 102, 241, 0.95);
|
|
22
|
+
border-radius: 2px;
|
|
23
|
+
transition: top 60ms ease-out, left 60ms ease-out, width 60ms ease-out, height 60ms ease-out;
|
|
24
|
+
}
|
|
25
|
+
.veo-pick-label {
|
|
26
|
+
position: fixed;
|
|
27
|
+
z-index: ${PICKER_Z};
|
|
28
|
+
pointer-events: none;
|
|
29
|
+
max-width: 360px;
|
|
30
|
+
background: #1e1b4b;
|
|
31
|
+
color: #e0e7ff;
|
|
32
|
+
font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
33
|
+
padding: 6px 9px;
|
|
34
|
+
border-radius: 6px;
|
|
35
|
+
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35);
|
|
36
|
+
}
|
|
37
|
+
.veo-pick-label .veo-pick-sel { color: #c7d2fe; word-break: break-all; }
|
|
38
|
+
.veo-pick-label .veo-pick-dim { color: #a5b4fc; margin-top: 3px; }
|
|
39
|
+
.veo-pick-hint {
|
|
40
|
+
position: fixed;
|
|
41
|
+
top: 12px;
|
|
42
|
+
left: 50%;
|
|
43
|
+
transform: translateX(-50%);
|
|
44
|
+
z-index: ${PICKER_Z};
|
|
45
|
+
pointer-events: none;
|
|
46
|
+
background: #4338ca;
|
|
47
|
+
color: #fff;
|
|
48
|
+
font: 13px/1 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
49
|
+
padding: 8px 14px;
|
|
50
|
+
border-radius: 999px;
|
|
51
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
|
|
52
|
+
}
|
|
53
|
+
`;
|
|
54
|
+
var ElementPicker = class {
|
|
55
|
+
constructor() {
|
|
56
|
+
this.host = null;
|
|
57
|
+
this.box = null;
|
|
58
|
+
this.label = null;
|
|
59
|
+
this.current = null;
|
|
60
|
+
this.maxAncestors = DEFAULT_MAX_ANCESTORS;
|
|
61
|
+
this.onPick = null;
|
|
62
|
+
this.onCancel = null;
|
|
63
|
+
this.onMove = (event) => {
|
|
64
|
+
const target = event.target;
|
|
65
|
+
if (!(target instanceof Element) || this.isOwn(target)) return;
|
|
66
|
+
this.current = target;
|
|
67
|
+
this.draw(target);
|
|
68
|
+
};
|
|
69
|
+
this.onClick = (event) => {
|
|
70
|
+
const target = this.current ?? (event.target instanceof Element ? event.target : null);
|
|
71
|
+
if (!target || this.isOwn(target)) return;
|
|
72
|
+
event.preventDefault();
|
|
73
|
+
event.stopPropagation();
|
|
74
|
+
event.stopImmediatePropagation();
|
|
75
|
+
const picked = this.describe(target);
|
|
76
|
+
const cb = this.onPick;
|
|
77
|
+
this.stop();
|
|
78
|
+
cb?.(picked);
|
|
79
|
+
};
|
|
80
|
+
this.onKey = (event) => {
|
|
81
|
+
if (event.key !== "Escape") return;
|
|
82
|
+
event.preventDefault();
|
|
83
|
+
event.stopPropagation();
|
|
84
|
+
const cb = this.onCancel;
|
|
85
|
+
this.stop();
|
|
86
|
+
cb?.();
|
|
87
|
+
};
|
|
88
|
+
/** Traga mousedown/mouseup durante el picking (evita focus/active de la app). */
|
|
89
|
+
this.swallow = (event) => {
|
|
90
|
+
if (this.isOwn(event.target)) return;
|
|
91
|
+
event.preventDefault();
|
|
92
|
+
event.stopPropagation();
|
|
93
|
+
};
|
|
94
|
+
this.reposition = () => {
|
|
95
|
+
if (this.current) this.draw(this.current);
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
start(options) {
|
|
99
|
+
this.stop();
|
|
100
|
+
this.maxAncestors = options.maxAncestors ?? DEFAULT_MAX_ANCESTORS;
|
|
101
|
+
this.onPick = options.onPick;
|
|
102
|
+
this.onCancel = options.onCancel ?? null;
|
|
103
|
+
const host = document.createElement("div");
|
|
104
|
+
host.setAttribute(PICKER_HOST_ATTR, "");
|
|
105
|
+
const shadow = host.attachShadow({ mode: "open" });
|
|
106
|
+
const style = document.createElement("style");
|
|
107
|
+
style.textContent = PICKER_STYLES;
|
|
108
|
+
this.box = document.createElement("div");
|
|
109
|
+
this.box.className = "veo-pick-box";
|
|
110
|
+
this.box.style.display = "none";
|
|
111
|
+
this.label = document.createElement("div");
|
|
112
|
+
this.label.className = "veo-pick-label";
|
|
113
|
+
this.label.style.display = "none";
|
|
114
|
+
const hint = document.createElement("div");
|
|
115
|
+
hint.className = "veo-pick-hint";
|
|
116
|
+
hint.textContent = options.hint ?? "Seleccion\xE1 un elemento \xB7 Esc para cancelar";
|
|
117
|
+
shadow.append(style, this.box, this.label, hint);
|
|
118
|
+
document.body.appendChild(host);
|
|
119
|
+
this.host = host;
|
|
120
|
+
document.addEventListener("mousemove", this.onMove, true);
|
|
121
|
+
document.addEventListener("click", this.onClick, true);
|
|
122
|
+
document.addEventListener("mousedown", this.swallow, true);
|
|
123
|
+
document.addEventListener("mouseup", this.swallow, true);
|
|
124
|
+
document.addEventListener("keydown", this.onKey, true);
|
|
125
|
+
window.addEventListener("scroll", this.reposition, true);
|
|
126
|
+
window.addEventListener("resize", this.reposition);
|
|
127
|
+
}
|
|
128
|
+
stop() {
|
|
129
|
+
document.removeEventListener("mousemove", this.onMove, true);
|
|
130
|
+
document.removeEventListener("click", this.onClick, true);
|
|
131
|
+
document.removeEventListener("mousedown", this.swallow, true);
|
|
132
|
+
document.removeEventListener("mouseup", this.swallow, true);
|
|
133
|
+
document.removeEventListener("keydown", this.onKey, true);
|
|
134
|
+
window.removeEventListener("scroll", this.reposition, true);
|
|
135
|
+
window.removeEventListener("resize", this.reposition);
|
|
136
|
+
if (this.host?.parentNode) this.host.parentNode.removeChild(this.host);
|
|
137
|
+
this.host = null;
|
|
138
|
+
this.box = null;
|
|
139
|
+
this.label = null;
|
|
140
|
+
this.current = null;
|
|
141
|
+
this.onPick = null;
|
|
142
|
+
this.onCancel = null;
|
|
143
|
+
}
|
|
144
|
+
/** ¿Es un nodo de nuestro propio overlay? (no debe ser seleccionable). */
|
|
145
|
+
isOwn(target) {
|
|
146
|
+
return target instanceof Node && this.host !== null && this.host.contains(target);
|
|
147
|
+
}
|
|
148
|
+
/** Dibuja el recuadro de resalte y la etiqueta sobre el elemento dado. */
|
|
149
|
+
draw(element) {
|
|
150
|
+
if (!this.box || !this.label) return;
|
|
151
|
+
const rect = element.getBoundingClientRect();
|
|
152
|
+
this.box.style.display = "block";
|
|
153
|
+
this.box.style.top = `${rect.top}px`;
|
|
154
|
+
this.box.style.left = `${rect.left}px`;
|
|
155
|
+
this.box.style.width = `${rect.width}px`;
|
|
156
|
+
this.box.style.height = `${rect.height}px`;
|
|
157
|
+
const shortSel = buildSelectorPath(element, 0);
|
|
158
|
+
this.label.textContent = "";
|
|
159
|
+
const sel = document.createElement("div");
|
|
160
|
+
sel.className = "veo-pick-sel";
|
|
161
|
+
sel.textContent = shortSel;
|
|
162
|
+
const dim = document.createElement("div");
|
|
163
|
+
dim.className = "veo-pick-dim";
|
|
164
|
+
dim.textContent = `${Math.round(rect.width)} \xD7 ${Math.round(rect.height)}`;
|
|
165
|
+
this.label.append(sel, dim);
|
|
166
|
+
this.label.style.display = "block";
|
|
167
|
+
const LABEL_H = 48;
|
|
168
|
+
const placeAbove = rect.bottom + 8 + LABEL_H > window.innerHeight;
|
|
169
|
+
this.label.style.top = placeAbove ? `${Math.max(8, rect.top - LABEL_H - 8)}px` : `${rect.bottom + 8}px`;
|
|
170
|
+
this.label.style.left = `${Math.max(8, rect.left)}px`;
|
|
171
|
+
}
|
|
172
|
+
describe(element) {
|
|
173
|
+
const rect = element.getBoundingClientRect();
|
|
174
|
+
return {
|
|
175
|
+
selector: buildSelectorPath(element, this.maxAncestors),
|
|
176
|
+
shortSelector: buildSelectorPath(element, 0),
|
|
177
|
+
tag: element.tagName.toLowerCase(),
|
|
178
|
+
id: element.id || null,
|
|
179
|
+
classes: filterHumanClasses(element),
|
|
180
|
+
text: (element.textContent ?? "").trim().slice(0, 120),
|
|
181
|
+
rect: {
|
|
182
|
+
x: Math.round(rect.left),
|
|
183
|
+
y: Math.round(rect.top),
|
|
184
|
+
width: Math.round(rect.width),
|
|
185
|
+
height: Math.round(rect.height)
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
var activePicker = null;
|
|
191
|
+
function startElementPicker(options) {
|
|
192
|
+
if (!hasDocument()) {
|
|
193
|
+
return { stop: () => {
|
|
194
|
+
} };
|
|
195
|
+
}
|
|
196
|
+
if (!activePicker) activePicker = new ElementPicker();
|
|
197
|
+
activePicker.start(options);
|
|
198
|
+
return { stop: () => activePicker?.stop() };
|
|
199
|
+
}
|
|
200
|
+
function stopElementPicker() {
|
|
201
|
+
activePicker?.stop();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// src/plugins/builder/builder-mode.ts
|
|
205
|
+
function initBuilderMode() {
|
|
206
|
+
if (!hasWindow() || !hasDocument()) return null;
|
|
207
|
+
let params;
|
|
208
|
+
try {
|
|
209
|
+
params = new URLSearchParams(window.location.search);
|
|
210
|
+
} catch {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
const token = params.get(BUILDER_TOKEN_PARAM);
|
|
214
|
+
if (!token) return null;
|
|
215
|
+
const dashboardOrigin = resolveDashboardOrigin(params);
|
|
216
|
+
if (!dashboardOrigin) return null;
|
|
217
|
+
const target = resolveTarget();
|
|
218
|
+
if (!target) return null;
|
|
219
|
+
let picker = null;
|
|
220
|
+
const post = (event) => {
|
|
221
|
+
target.postMessage({ source: VEO_BUILDER_SOURCE, token, ...event }, dashboardOrigin);
|
|
222
|
+
};
|
|
223
|
+
const handleCommand = (cmd) => {
|
|
224
|
+
switch (cmd.type) {
|
|
225
|
+
case "start-picker":
|
|
226
|
+
picker?.stop();
|
|
227
|
+
picker = startElementPicker({
|
|
228
|
+
onPick: (payload) => {
|
|
229
|
+
picker = null;
|
|
230
|
+
post({ type: "picked", payload });
|
|
231
|
+
},
|
|
232
|
+
onCancel: () => {
|
|
233
|
+
picker = null;
|
|
234
|
+
post({ type: "pick-cancelled" });
|
|
235
|
+
},
|
|
236
|
+
...cmd.maxAncestors !== void 0 ? { maxAncestors: cmd.maxAncestors } : {},
|
|
237
|
+
...cmd.hint !== void 0 ? { hint: cmd.hint } : {}
|
|
238
|
+
});
|
|
239
|
+
break;
|
|
240
|
+
case "stop-picker":
|
|
241
|
+
picker?.stop();
|
|
242
|
+
picker = null;
|
|
243
|
+
break;
|
|
244
|
+
case "preview":
|
|
245
|
+
if (cmd.guide) void previewGuide(cmd.guide).ready;
|
|
246
|
+
break;
|
|
247
|
+
case "close-preview":
|
|
248
|
+
closeGuidePreview();
|
|
249
|
+
break;
|
|
250
|
+
case "teardown":
|
|
251
|
+
teardown();
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
const onMessage = (event) => {
|
|
256
|
+
if (event.origin !== dashboardOrigin) return;
|
|
257
|
+
const data = event.data;
|
|
258
|
+
if (!data || data.source !== VEO_BUILDER_SOURCE || data.token !== token) return;
|
|
259
|
+
handleCommand(data);
|
|
260
|
+
};
|
|
261
|
+
const onPageHide = () => post({ type: "closed" });
|
|
262
|
+
const teardown = () => {
|
|
263
|
+
window.removeEventListener("message", onMessage);
|
|
264
|
+
window.removeEventListener("pagehide", onPageHide);
|
|
265
|
+
picker?.stop();
|
|
266
|
+
picker = null;
|
|
267
|
+
closeGuidePreview();
|
|
268
|
+
post({ type: "closed" });
|
|
269
|
+
};
|
|
270
|
+
const activate = () => {
|
|
271
|
+
window.addEventListener("message", onMessage);
|
|
272
|
+
window.addEventListener("pagehide", onPageHide);
|
|
273
|
+
post({ type: "ready" });
|
|
274
|
+
};
|
|
275
|
+
const cfg = window.__veoBuilder;
|
|
276
|
+
if (cfg?.apiKey && cfg?.apiUrl) {
|
|
277
|
+
void verifyBuilderToken(cfg.apiUrl, cfg.apiKey, token).then((ok) => {
|
|
278
|
+
if (ok) activate();
|
|
279
|
+
else if (window.console) console.warn("[veo] token de builder inv\xE1lido \u2014 modo no activado");
|
|
280
|
+
});
|
|
281
|
+
} else {
|
|
282
|
+
activate();
|
|
283
|
+
}
|
|
284
|
+
return { teardown };
|
|
285
|
+
}
|
|
286
|
+
async function verifyBuilderToken(apiUrl, apiKey, token) {
|
|
287
|
+
try {
|
|
288
|
+
const url = `${apiUrl.replace(/\/+$/, "")}/v1/builder/verify`;
|
|
289
|
+
const res = await fetch(url, {
|
|
290
|
+
method: "POST",
|
|
291
|
+
headers: { "Content-Type": "application/json", "X-Api-Key": apiKey },
|
|
292
|
+
body: JSON.stringify({ token })
|
|
293
|
+
});
|
|
294
|
+
if (!res.ok) return true;
|
|
295
|
+
const data = await res.json().catch(() => null);
|
|
296
|
+
return data?.valid !== false;
|
|
297
|
+
} catch {
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function resolveTarget() {
|
|
302
|
+
if (window.opener && window.opener !== window) return window.opener;
|
|
303
|
+
if (window.parent && window.parent !== window) return window.parent;
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
function resolveDashboardOrigin(params) {
|
|
307
|
+
const explicit = params.get(BUILDER_ORIGIN_PARAM);
|
|
308
|
+
if (explicit) {
|
|
309
|
+
try {
|
|
310
|
+
return new URL(explicit).origin;
|
|
311
|
+
} catch {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (document.referrer) {
|
|
316
|
+
try {
|
|
317
|
+
return new URL(document.referrer).origin;
|
|
318
|
+
} catch {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// src/plugins/builder/builder-session.ts
|
|
326
|
+
var DEFAULT_FEATURES = "popup,width=1280,height=860";
|
|
327
|
+
var POPUP_POLL_MS = 600;
|
|
328
|
+
function createBuilderSession(options) {
|
|
329
|
+
if (!hasWindow()) return noopSession();
|
|
330
|
+
const dashboardOrigin = options.dashboardOrigin ?? window.location.origin;
|
|
331
|
+
const appOrigin = safeOrigin(options.appUrl);
|
|
332
|
+
const url = buildAppUrl(options.appUrl, options.token, dashboardOrigin);
|
|
333
|
+
const frame = options.frame ?? null;
|
|
334
|
+
const useFrame = frame !== null;
|
|
335
|
+
let popup = null;
|
|
336
|
+
if (useFrame) {
|
|
337
|
+
frame.src = url;
|
|
338
|
+
} else {
|
|
339
|
+
popup = window.open(
|
|
340
|
+
url,
|
|
341
|
+
options.windowName ?? "veo-builder",
|
|
342
|
+
options.windowFeatures ?? DEFAULT_FEATURES
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
const targetWindow = () => useFrame ? frame?.contentWindow ?? null : popup;
|
|
346
|
+
const post = (command) => {
|
|
347
|
+
const w = targetWindow();
|
|
348
|
+
if (!w) return;
|
|
349
|
+
if (!useFrame && popup?.closed) return;
|
|
350
|
+
w.postMessage(
|
|
351
|
+
{ source: VEO_BUILDER_SOURCE, token: options.token, ...command },
|
|
352
|
+
appOrigin ?? "*"
|
|
353
|
+
);
|
|
354
|
+
};
|
|
355
|
+
let pollTimer;
|
|
356
|
+
const cleanup = () => {
|
|
357
|
+
window.removeEventListener("message", onMessage);
|
|
358
|
+
if (pollTimer !== void 0) {
|
|
359
|
+
clearInterval(pollTimer);
|
|
360
|
+
pollTimer = void 0;
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
const onMessage = (event) => {
|
|
364
|
+
if (appOrigin && event.origin !== appOrigin) return;
|
|
365
|
+
const data = event.data;
|
|
366
|
+
if (!data || data.source !== VEO_BUILDER_SOURCE || data.token !== options.token) return;
|
|
367
|
+
switch (data.type) {
|
|
368
|
+
case "ready":
|
|
369
|
+
options.onReady?.();
|
|
370
|
+
break;
|
|
371
|
+
case "picked":
|
|
372
|
+
if (data.payload) options.onPicked?.(data.payload);
|
|
373
|
+
break;
|
|
374
|
+
case "pick-cancelled":
|
|
375
|
+
options.onCancelled?.();
|
|
376
|
+
break;
|
|
377
|
+
case "closed":
|
|
378
|
+
cleanup();
|
|
379
|
+
options.onClosed?.();
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
window.addEventListener("message", onMessage);
|
|
384
|
+
if (!useFrame) {
|
|
385
|
+
pollTimer = setInterval(() => {
|
|
386
|
+
if (popup?.closed) {
|
|
387
|
+
cleanup();
|
|
388
|
+
options.onClosed?.();
|
|
389
|
+
}
|
|
390
|
+
}, POPUP_POLL_MS);
|
|
391
|
+
}
|
|
392
|
+
return {
|
|
393
|
+
startPicker: (o) => post({
|
|
394
|
+
type: "start-picker",
|
|
395
|
+
...o?.maxAncestors !== void 0 ? { maxAncestors: o.maxAncestors } : {},
|
|
396
|
+
...o?.hint !== void 0 ? { hint: o.hint } : {}
|
|
397
|
+
}),
|
|
398
|
+
stopPicker: () => post({ type: "stop-picker" }),
|
|
399
|
+
preview: (guide) => post({ type: "preview", guide }),
|
|
400
|
+
closePreview: () => post({ type: "close-preview" }),
|
|
401
|
+
close: () => {
|
|
402
|
+
post({ type: "teardown" });
|
|
403
|
+
if (useFrame) {
|
|
404
|
+
if (frame) frame.src = "about:blank";
|
|
405
|
+
} else if (popup && !popup.closed) {
|
|
406
|
+
popup.close();
|
|
407
|
+
}
|
|
408
|
+
cleanup();
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
function buildAppUrl(appUrl, token, dashboardOrigin) {
|
|
413
|
+
try {
|
|
414
|
+
const u = new URL(appUrl, window.location.href);
|
|
415
|
+
u.searchParams.set(BUILDER_TOKEN_PARAM, token);
|
|
416
|
+
u.searchParams.set(BUILDER_ORIGIN_PARAM, dashboardOrigin);
|
|
417
|
+
return u.toString();
|
|
418
|
+
} catch {
|
|
419
|
+
return appUrl;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
function safeOrigin(url) {
|
|
423
|
+
try {
|
|
424
|
+
return new URL(url, window.location.href).origin;
|
|
425
|
+
} catch {
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
function noopSession() {
|
|
430
|
+
return {
|
|
431
|
+
startPicker: () => {
|
|
432
|
+
},
|
|
433
|
+
stopPicker: () => {
|
|
434
|
+
},
|
|
435
|
+
preview: () => {
|
|
436
|
+
},
|
|
437
|
+
closePreview: () => {
|
|
438
|
+
},
|
|
439
|
+
close: () => {
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// src/builder.ts
|
|
445
|
+
initBuilderMode();
|
|
446
|
+
|
|
447
|
+
export { createBuilderSession, initBuilderMode, startElementPicker, stopElementPicker };
|
|
448
|
+
//# sourceMappingURL=builder.mjs.map
|
|
449
|
+
//# sourceMappingURL=builder.mjs.map
|