veo-sdk 0.2.0 → 0.3.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.
@@ -0,0 +1,108 @@
1
+ /** Cookie que persiste el token del builder (particionada, contexto embebido). */
2
+ declare const VEO_BUILDER_COOKIE = "veo-builder";
3
+ /** Cookie que persiste el origen del dashboard de Veo. */
4
+ declare const VEO_BUILDER_ORIGIN_COOKIE = "veo-builder-origin";
5
+ /** Sesión de builder activa (la app está embebida por el editor de Veo). */
6
+ interface VeoBuilderContext {
7
+ /** Token de correlación del handshake (idealmente firmado por el backend). */
8
+ token: string;
9
+ /** Origen del dashboard que embebe, para `frame-ancestors`. */
10
+ origin: string | null;
11
+ }
12
+ interface CookieLike {
13
+ name: string;
14
+ value: string;
15
+ path?: string;
16
+ domain?: string;
17
+ maxAge?: number;
18
+ expires?: Date | number;
19
+ httpOnly?: boolean;
20
+ sameSite?: boolean | 'lax' | 'strict' | 'none';
21
+ secure?: boolean;
22
+ partitioned?: boolean;
23
+ priority?: 'low' | 'medium' | 'high';
24
+ }
25
+ interface VeoBuilderRequest {
26
+ nextUrl: {
27
+ searchParams: URLSearchParams;
28
+ };
29
+ cookies: {
30
+ get(name: string): {
31
+ value: string;
32
+ } | undefined;
33
+ };
34
+ }
35
+ interface VeoBuilderResponse {
36
+ headers: {
37
+ set(name: string, value: string): void;
38
+ delete(name: string): void;
39
+ };
40
+ cookies: {
41
+ set(cookie: CookieLike): void;
42
+ getAll(): CookieLike[];
43
+ };
44
+ }
45
+ interface VeoBuilderProxyOptions {
46
+ /**
47
+ * Orígenes autorizados a embeber la app (`frame-ancestors`), p.ej.
48
+ * `['https://veo-dashboard-opal.vercel.app']`. Si se omite o queda vacío, se
49
+ * usa el origen que envía el propio editor (`veoBuilderOrigin`).
50
+ */
51
+ dashboardOrigins?: string[];
52
+ /** TTL de las cookies del modo builder, en segundos. Default 1800 (30 min). */
53
+ cookieMaxAge?: number;
54
+ }
55
+ /**
56
+ * Lee el contexto de builder del request: del query param (`?veoBuilder`, primer
57
+ * arranque dentro del iframe) o de la cookie (sobrevive navegaciones SPA y
58
+ * requests RSC que ya no llevan el param). `null` si la app corre en modo normal.
59
+ */
60
+ declare function readVeoBuilderContext(request: VeoBuilderRequest): VeoBuilderContext | null;
61
+ /** Cliente de auth mínimo para la sesión de diseño. Compatible con `supabase.auth`
62
+ * y cualquier auth con la misma forma (tipos estructurales, sin acoplar el SDK). */
63
+ interface DesignAuthClient {
64
+ getUser(): Promise<{
65
+ data: {
66
+ user: unknown;
67
+ };
68
+ }>;
69
+ signInWithPassword(credentials: {
70
+ email: string;
71
+ password: string;
72
+ }): Promise<unknown>;
73
+ }
74
+ interface DesignSessionOptions {
75
+ /** Email del usuario demo. Default: env `VEO_BUILDER_DEMO_EMAIL`. */
76
+ email?: string;
77
+ /** Password del usuario demo. Default: env `VEO_BUILDER_DEMO_PASSWORD`. */
78
+ password?: string;
79
+ }
80
+ /**
81
+ * Establece la "sesión de diseño" cuando la app está embebida por el editor de
82
+ * Veo y no hay login: entra como usuario DEMO (sin datos reales) para poder
83
+ * navegar la app sin auth. No-op fuera de modo builder, si ya hay sesión, o si
84
+ * faltan credenciales. Agnóstico del proveedor: funciona con `supabase.auth` y
85
+ * cualquier cliente con `getUser` + `signInWithPassword`.
86
+ *
87
+ * @example
88
+ * ```ts
89
+ * await ensureVeoDesignSession(builder, supabase.auth);
90
+ * ```
91
+ */
92
+ declare function ensureVeoDesignSession(builder: VeoBuilderContext | null, auth: DesignAuthClient, options?: DesignSessionOptions): Promise<void>;
93
+ /**
94
+ * Envuelve tu middleware de Next para habilitar el modo diseño de Veo. Cuando la
95
+ * app está embebida (`?veoBuilder` o cookie), DESPUÉS de correr tu handler:
96
+ * - reescribe TODAS las cookies de la respuesta a `SameSite=None; Secure;
97
+ * Partitioned` (así la sesión viaja en el iframe cross-site),
98
+ * - persiste token + origen en cookies particionadas,
99
+ * - setea `frame-ancestors` y saca `X-Frame-Options`.
100
+ *
101
+ * Tu handler recibe `{ builder }` para decidir la sesión de diseño (lo único
102
+ * app-specific). Fuera de modo builder es un passthrough exacto (no toca nada).
103
+ */
104
+ declare function veoBuilderProxy<Req extends VeoBuilderRequest, Res extends VeoBuilderResponse>(handler: (request: Req, ctx: {
105
+ builder: VeoBuilderContext | null;
106
+ }) => Res | Promise<Res>, options?: VeoBuilderProxyOptions): (request: Req) => Promise<Res>;
107
+
108
+ export { type DesignAuthClient, type DesignSessionOptions, VEO_BUILDER_COOKIE, VEO_BUILDER_ORIGIN_COOKIE, type VeoBuilderContext, type VeoBuilderProxyOptions, type VeoBuilderRequest, type VeoBuilderResponse, ensureVeoDesignSession, readVeoBuilderContext, veoBuilderProxy };
package/dist/next.d.ts ADDED
@@ -0,0 +1,108 @@
1
+ /** Cookie que persiste el token del builder (particionada, contexto embebido). */
2
+ declare const VEO_BUILDER_COOKIE = "veo-builder";
3
+ /** Cookie que persiste el origen del dashboard de Veo. */
4
+ declare const VEO_BUILDER_ORIGIN_COOKIE = "veo-builder-origin";
5
+ /** Sesión de builder activa (la app está embebida por el editor de Veo). */
6
+ interface VeoBuilderContext {
7
+ /** Token de correlación del handshake (idealmente firmado por el backend). */
8
+ token: string;
9
+ /** Origen del dashboard que embebe, para `frame-ancestors`. */
10
+ origin: string | null;
11
+ }
12
+ interface CookieLike {
13
+ name: string;
14
+ value: string;
15
+ path?: string;
16
+ domain?: string;
17
+ maxAge?: number;
18
+ expires?: Date | number;
19
+ httpOnly?: boolean;
20
+ sameSite?: boolean | 'lax' | 'strict' | 'none';
21
+ secure?: boolean;
22
+ partitioned?: boolean;
23
+ priority?: 'low' | 'medium' | 'high';
24
+ }
25
+ interface VeoBuilderRequest {
26
+ nextUrl: {
27
+ searchParams: URLSearchParams;
28
+ };
29
+ cookies: {
30
+ get(name: string): {
31
+ value: string;
32
+ } | undefined;
33
+ };
34
+ }
35
+ interface VeoBuilderResponse {
36
+ headers: {
37
+ set(name: string, value: string): void;
38
+ delete(name: string): void;
39
+ };
40
+ cookies: {
41
+ set(cookie: CookieLike): void;
42
+ getAll(): CookieLike[];
43
+ };
44
+ }
45
+ interface VeoBuilderProxyOptions {
46
+ /**
47
+ * Orígenes autorizados a embeber la app (`frame-ancestors`), p.ej.
48
+ * `['https://veo-dashboard-opal.vercel.app']`. Si se omite o queda vacío, se
49
+ * usa el origen que envía el propio editor (`veoBuilderOrigin`).
50
+ */
51
+ dashboardOrigins?: string[];
52
+ /** TTL de las cookies del modo builder, en segundos. Default 1800 (30 min). */
53
+ cookieMaxAge?: number;
54
+ }
55
+ /**
56
+ * Lee el contexto de builder del request: del query param (`?veoBuilder`, primer
57
+ * arranque dentro del iframe) o de la cookie (sobrevive navegaciones SPA y
58
+ * requests RSC que ya no llevan el param). `null` si la app corre en modo normal.
59
+ */
60
+ declare function readVeoBuilderContext(request: VeoBuilderRequest): VeoBuilderContext | null;
61
+ /** Cliente de auth mínimo para la sesión de diseño. Compatible con `supabase.auth`
62
+ * y cualquier auth con la misma forma (tipos estructurales, sin acoplar el SDK). */
63
+ interface DesignAuthClient {
64
+ getUser(): Promise<{
65
+ data: {
66
+ user: unknown;
67
+ };
68
+ }>;
69
+ signInWithPassword(credentials: {
70
+ email: string;
71
+ password: string;
72
+ }): Promise<unknown>;
73
+ }
74
+ interface DesignSessionOptions {
75
+ /** Email del usuario demo. Default: env `VEO_BUILDER_DEMO_EMAIL`. */
76
+ email?: string;
77
+ /** Password del usuario demo. Default: env `VEO_BUILDER_DEMO_PASSWORD`. */
78
+ password?: string;
79
+ }
80
+ /**
81
+ * Establece la "sesión de diseño" cuando la app está embebida por el editor de
82
+ * Veo y no hay login: entra como usuario DEMO (sin datos reales) para poder
83
+ * navegar la app sin auth. No-op fuera de modo builder, si ya hay sesión, o si
84
+ * faltan credenciales. Agnóstico del proveedor: funciona con `supabase.auth` y
85
+ * cualquier cliente con `getUser` + `signInWithPassword`.
86
+ *
87
+ * @example
88
+ * ```ts
89
+ * await ensureVeoDesignSession(builder, supabase.auth);
90
+ * ```
91
+ */
92
+ declare function ensureVeoDesignSession(builder: VeoBuilderContext | null, auth: DesignAuthClient, options?: DesignSessionOptions): Promise<void>;
93
+ /**
94
+ * Envuelve tu middleware de Next para habilitar el modo diseño de Veo. Cuando la
95
+ * app está embebida (`?veoBuilder` o cookie), DESPUÉS de correr tu handler:
96
+ * - reescribe TODAS las cookies de la respuesta a `SameSite=None; Secure;
97
+ * Partitioned` (así la sesión viaja en el iframe cross-site),
98
+ * - persiste token + origen en cookies particionadas,
99
+ * - setea `frame-ancestors` y saca `X-Frame-Options`.
100
+ *
101
+ * Tu handler recibe `{ builder }` para decidir la sesión de diseño (lo único
102
+ * app-specific). Fuera de modo builder es un passthrough exacto (no toca nada).
103
+ */
104
+ declare function veoBuilderProxy<Req extends VeoBuilderRequest, Res extends VeoBuilderResponse>(handler: (request: Req, ctx: {
105
+ builder: VeoBuilderContext | null;
106
+ }) => Res | Promise<Res>, options?: VeoBuilderProxyOptions): (request: Req) => Promise<Res>;
107
+
108
+ export { type DesignAuthClient, type DesignSessionOptions, VEO_BUILDER_COOKIE, VEO_BUILDER_ORIGIN_COOKIE, type VeoBuilderContext, type VeoBuilderProxyOptions, type VeoBuilderRequest, type VeoBuilderResponse, ensureVeoDesignSession, readVeoBuilderContext, veoBuilderProxy };
package/dist/next.mjs ADDED
@@ -0,0 +1,82 @@
1
+ import { BUILDER_TOKEN_PARAM, BUILDER_ORIGIN_PARAM } from './chunk-JIU5JLI3.mjs';
2
+
3
+ // src/next.ts
4
+ var VEO_BUILDER_COOKIE = "veo-builder";
5
+ var VEO_BUILDER_ORIGIN_COOKIE = "veo-builder-origin";
6
+ function readVeoBuilderContext(request) {
7
+ const params = request.nextUrl.searchParams;
8
+ const fromParam = params.get(BUILDER_TOKEN_PARAM);
9
+ if (fromParam) {
10
+ return {
11
+ token: fromParam,
12
+ origin: params.get(BUILDER_ORIGIN_PARAM) ?? request.cookies.get(VEO_BUILDER_ORIGIN_COOKIE)?.value ?? null
13
+ };
14
+ }
15
+ const fromCookie = request.cookies.get(VEO_BUILDER_COOKIE)?.value;
16
+ if (fromCookie) {
17
+ return {
18
+ token: fromCookie,
19
+ origin: request.cookies.get(VEO_BUILDER_ORIGIN_COOKIE)?.value ?? null
20
+ };
21
+ }
22
+ return null;
23
+ }
24
+ async function ensureVeoDesignSession(builder, auth, options = {}) {
25
+ if (!builder) return;
26
+ const email = options.email ?? readEnv("VEO_BUILDER_DEMO_EMAIL");
27
+ const password = options.password ?? readEnv("VEO_BUILDER_DEMO_PASSWORD");
28
+ if (!email || !password) return;
29
+ const {
30
+ data: { user }
31
+ } = await auth.getUser();
32
+ if (!user) await auth.signInWithPassword({ email, password });
33
+ }
34
+ function readEnv(key) {
35
+ try {
36
+ return typeof process !== "undefined" ? process.env?.[key] : void 0;
37
+ } catch {
38
+ return void 0;
39
+ }
40
+ }
41
+ function veoBuilderProxy(handler, options = {}) {
42
+ return async (request) => {
43
+ const builder = readVeoBuilderContext(request);
44
+ const response = await handler(request, { builder });
45
+ if (builder) {
46
+ repartitionCookies(response);
47
+ persistBuilderCookies(response, builder, options.cookieMaxAge ?? 1800);
48
+ applyFramingHeaders(response, builder, options.dashboardOrigins);
49
+ }
50
+ return response;
51
+ };
52
+ }
53
+ function repartitionCookies(response) {
54
+ for (const cookie of response.cookies.getAll()) {
55
+ response.cookies.set({ ...cookie, sameSite: "none", secure: true, partitioned: true });
56
+ }
57
+ }
58
+ function persistBuilderCookies(response, ctx, maxAge) {
59
+ const base = { path: "/", maxAge, sameSite: "none", secure: true, partitioned: true };
60
+ response.cookies.set({ name: VEO_BUILDER_COOKIE, value: ctx.token, httpOnly: true, ...base });
61
+ if (ctx.origin) {
62
+ response.cookies.set({
63
+ name: VEO_BUILDER_ORIGIN_COOKIE,
64
+ value: ctx.origin,
65
+ httpOnly: false,
66
+ ...base
67
+ });
68
+ }
69
+ }
70
+ function applyFramingHeaders(response, ctx, dashboardOrigins) {
71
+ const configured = (dashboardOrigins ?? []).map((s) => s.trim()).filter(Boolean);
72
+ const origins = configured.length > 0 ? configured : ctx.origin ? [ctx.origin] : [];
73
+ response.headers.delete("X-Frame-Options");
74
+ response.headers.set(
75
+ "Content-Security-Policy",
76
+ `frame-ancestors 'self' ${origins.join(" ")}`.trim()
77
+ );
78
+ }
79
+
80
+ export { VEO_BUILDER_COOKIE, VEO_BUILDER_ORIGIN_COOKIE, ensureVeoDesignSession, readVeoBuilderContext, veoBuilderProxy };
81
+ //# sourceMappingURL=next.mjs.map
82
+ //# sourceMappingURL=next.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/next.ts"],"names":[],"mappings":"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","file":"next.mjs","sourcesContent":["/**\n * `veo-sdk/next` — helper de middleware para el \"modo diseño\" del editor visual.\n *\n * El editor de Veo embebe tu app en un `<iframe>` con `?veoBuilder=<token>`. Para\n * que tu app se deje diseñar hacen falta 3 cosas MECÁNICAS:\n * 1. permitir el embed (`frame-ancestors`, sacar `X-Frame-Options`),\n * 2. que las cookies de sesión viajen en el iframe cross-site\n * (`SameSite=None; Secure; Partitioned` — CHIPS),\n * 3. persistir el modo builder entre navegaciones (cookie particionada), así\n * sobrevive al redirect del guard de auth y a un login.\n *\n * Este wrapper hace las 3. Lo ÚNICO app-specific — qué sesión renderizás sin\n * login (usuario demo, etc.) — queda en tu handler, que recibe `{ builder }`.\n *\n * No depende de `next` (usa tipos estructurales), así el SDK sigue sin\n * dependencias de runtime. `NextRequest`/`NextResponse` son compatibles.\n *\n * @example\n * ```ts\n * // middleware.ts (o proxy.ts en Next 16)\n * import { veoBuilderProxy } from 'veo-sdk/next';\n *\n * export default veoBuilderProxy(\n * async (request, { builder }) => {\n * const { supabase, response } = createSupabaseMiddleware(request);\n * const { data: { user } } = await supabase.auth.getUser();\n * // Lo único tuyo: la sesión de diseño cuando estás embebido y sin login.\n * if (builder && !user) {\n * await supabase.auth.signInWithPassword({\n * email: process.env.VEO_BUILDER_DEMO_EMAIL!,\n * password: process.env.VEO_BUILDER_DEMO_PASSWORD!,\n * });\n * }\n * return response;\n * },\n * { dashboardOrigins: [process.env.VEO_DASHBOARD_ORIGINS ?? ''] },\n * );\n * ```\n */\nimport { BUILDER_ORIGIN_PARAM, BUILDER_TOKEN_PARAM } from './core/builder-constants';\n\n/** Cookie que persiste el token del builder (particionada, contexto embebido). */\nexport const VEO_BUILDER_COOKIE = 'veo-builder';\n/** Cookie que persiste el origen del dashboard de Veo. */\nexport const VEO_BUILDER_ORIGIN_COOKIE = 'veo-builder-origin';\n\n/** Sesión de builder activa (la app está embebida por el editor de Veo). */\nexport interface VeoBuilderContext {\n /** Token de correlación del handshake (idealmente firmado por el backend). */\n token: string;\n /** Origen del dashboard que embebe, para `frame-ancestors`. */\n origin: string | null;\n}\n\n// ── Tipos estructurales, compatibles con NextRequest/NextResponse ──\ninterface CookieLike {\n name: string;\n value: string;\n path?: string;\n domain?: string;\n maxAge?: number;\n expires?: Date | number;\n httpOnly?: boolean;\n sameSite?: boolean | 'lax' | 'strict' | 'none';\n secure?: boolean;\n partitioned?: boolean;\n priority?: 'low' | 'medium' | 'high';\n}\n\nexport interface VeoBuilderRequest {\n nextUrl: { searchParams: URLSearchParams };\n cookies: { get(name: string): { value: string } | undefined };\n}\n\nexport interface VeoBuilderResponse {\n headers: { set(name: string, value: string): void; delete(name: string): void };\n cookies: {\n set(cookie: CookieLike): void;\n getAll(): CookieLike[];\n };\n}\n\nexport interface VeoBuilderProxyOptions {\n /**\n * Orígenes autorizados a embeber la app (`frame-ancestors`), p.ej.\n * `['https://veo-dashboard-opal.vercel.app']`. Si se omite o queda vacío, se\n * usa el origen que envía el propio editor (`veoBuilderOrigin`).\n */\n dashboardOrigins?: string[];\n /** TTL de las cookies del modo builder, en segundos. Default 1800 (30 min). */\n cookieMaxAge?: number;\n}\n\n/**\n * Lee el contexto de builder del request: del query param (`?veoBuilder`, primer\n * arranque dentro del iframe) o de la cookie (sobrevive navegaciones SPA y\n * requests RSC que ya no llevan el param). `null` si la app corre en modo normal.\n */\nexport function readVeoBuilderContext(request: VeoBuilderRequest): VeoBuilderContext | null {\n const params = request.nextUrl.searchParams;\n const fromParam = params.get(BUILDER_TOKEN_PARAM);\n if (fromParam) {\n return {\n token: fromParam,\n origin:\n params.get(BUILDER_ORIGIN_PARAM) ??\n request.cookies.get(VEO_BUILDER_ORIGIN_COOKIE)?.value ??\n null,\n };\n }\n const fromCookie = request.cookies.get(VEO_BUILDER_COOKIE)?.value;\n if (fromCookie) {\n return {\n token: fromCookie,\n origin: request.cookies.get(VEO_BUILDER_ORIGIN_COOKIE)?.value ?? null,\n };\n }\n return null;\n}\n\n/** Cliente de auth mínimo para la sesión de diseño. Compatible con `supabase.auth`\n * y cualquier auth con la misma forma (tipos estructurales, sin acoplar el SDK). */\nexport interface DesignAuthClient {\n getUser(): Promise<{ data: { user: unknown } }>;\n signInWithPassword(credentials: { email: string; password: string }): Promise<unknown>;\n}\n\nexport interface DesignSessionOptions {\n /** Email del usuario demo. Default: env `VEO_BUILDER_DEMO_EMAIL`. */\n email?: string;\n /** Password del usuario demo. Default: env `VEO_BUILDER_DEMO_PASSWORD`. */\n password?: string;\n}\n\n/**\n * Establece la \"sesión de diseño\" cuando la app está embebida por el editor de\n * Veo y no hay login: entra como usuario DEMO (sin datos reales) para poder\n * navegar la app sin auth. No-op fuera de modo builder, si ya hay sesión, o si\n * faltan credenciales. Agnóstico del proveedor: funciona con `supabase.auth` y\n * cualquier cliente con `getUser` + `signInWithPassword`.\n *\n * @example\n * ```ts\n * await ensureVeoDesignSession(builder, supabase.auth);\n * ```\n */\nexport async function ensureVeoDesignSession(\n builder: VeoBuilderContext | null,\n auth: DesignAuthClient,\n options: DesignSessionOptions = {},\n): Promise<void> {\n if (!builder) return;\n const email = options.email ?? readEnv('VEO_BUILDER_DEMO_EMAIL');\n const password = options.password ?? readEnv('VEO_BUILDER_DEMO_PASSWORD');\n if (!email || !password) return;\n const {\n data: { user },\n } = await auth.getUser();\n if (!user) await auth.signInWithPassword({ email, password });\n}\n\nfunction readEnv(key: string): string | undefined {\n try {\n return typeof process !== 'undefined' ? process.env?.[key] : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Envuelve tu middleware de Next para habilitar el modo diseño de Veo. Cuando la\n * app está embebida (`?veoBuilder` o cookie), DESPUÉS de correr tu handler:\n * - reescribe TODAS las cookies de la respuesta a `SameSite=None; Secure;\n * Partitioned` (así la sesión viaja en el iframe cross-site),\n * - persiste token + origen en cookies particionadas,\n * - setea `frame-ancestors` y saca `X-Frame-Options`.\n *\n * Tu handler recibe `{ builder }` para decidir la sesión de diseño (lo único\n * app-specific). Fuera de modo builder es un passthrough exacto (no toca nada).\n */\nexport function veoBuilderProxy<Req extends VeoBuilderRequest, Res extends VeoBuilderResponse>(\n handler: (request: Req, ctx: { builder: VeoBuilderContext | null }) => Res | Promise<Res>,\n options: VeoBuilderProxyOptions = {},\n): (request: Req) => Promise<Res> {\n return async (request: Req): Promise<Res> => {\n const builder = readVeoBuilderContext(request);\n const response = await handler(request, { builder });\n if (builder) {\n repartitionCookies(response);\n persistBuilderCookies(response, builder, options.cookieMaxAge ?? 1800);\n applyFramingHeaders(response, builder, options.dashboardOrigins);\n }\n return response;\n };\n}\n\n/** Reescribe todas las cookies de la respuesta a particionadas (CHIPS). */\nfunction repartitionCookies(response: VeoBuilderResponse): void {\n for (const cookie of response.cookies.getAll()) {\n response.cookies.set({ ...cookie, sameSite: 'none', secure: true, partitioned: true });\n }\n}\n\nfunction persistBuilderCookies(\n response: VeoBuilderResponse,\n ctx: VeoBuilderContext,\n maxAge: number,\n): void {\n const base = { path: '/', maxAge, sameSite: 'none' as const, secure: true, partitioned: true };\n response.cookies.set({ name: VEO_BUILDER_COOKIE, value: ctx.token, httpOnly: true, ...base });\n if (ctx.origin) {\n response.cookies.set({\n name: VEO_BUILDER_ORIGIN_COOKIE,\n value: ctx.origin,\n httpOnly: false,\n ...base,\n });\n }\n}\n\nfunction applyFramingHeaders(\n response: VeoBuilderResponse,\n ctx: VeoBuilderContext,\n dashboardOrigins?: string[],\n): void {\n const configured = (dashboardOrigins ?? []).map((s) => s.trim()).filter(Boolean);\n const origins = configured.length > 0 ? configured : ctx.origin ? [ctx.origin] : [];\n response.headers.delete('X-Frame-Options');\n response.headers.set(\n 'Content-Security-Policy',\n `frame-ancestors 'self' ${origins.join(' ')}`.trim(),\n );\n}\n"]}