libreria-astro-lefebvre 0.0.55 → 0.0.57
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/package.json +1 -1
- package/src/components/Astro/Contenido_2025_Malaga.astro +18 -8
- package/src/index.ts +8 -2
- package/src/lib/functions.js +161 -6
- package/src/limbo/index.ts +8 -0
- package/src/limbo/init.ts +0 -7
- package/src/limbo/tokenHandler.ts +207 -0
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
import { extractImageUrl } from '../../lib/functions.js';
|
|
2
|
+
import { extractImageUrl, extractAllCrops } from '../../lib/functions.js';
|
|
3
3
|
|
|
4
4
|
const {
|
|
5
5
|
title,
|
|
@@ -19,7 +19,18 @@ const {
|
|
|
19
19
|
alignItems = 'start',
|
|
20
20
|
} = Astro.props;
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
// Extraer todos los crops definidos en el carbin (base, skyscraper)
|
|
23
|
+
// Funciona igual con JSON de PageBuilder o con objeto simple { base: "url", skyscraper: "url" }
|
|
24
|
+
const crops = extractAllCrops(image) as Record<string, string>;
|
|
25
|
+
const imageBase = crops['base'] || crops['original'] || crops['default'] || '';
|
|
26
|
+
const imageSkyscraper = crops['skyscraper'] || ''; // Disponible para uso en el template
|
|
27
|
+
|
|
28
|
+
// También se podría usar getCropByName si se quiere lógica más compleja
|
|
29
|
+
// const baseUrl = getCropByName(image, 'base');
|
|
30
|
+
// const skyUrl = getCropByName(image, 'skyscraper', { fallback: baseUrl });
|
|
31
|
+
|
|
32
|
+
// Fallback: si solo viene una URL directa, usar extractImageUrl
|
|
33
|
+
const imageUrl = imageBase || extractImageUrl(image);
|
|
23
34
|
---
|
|
24
35
|
|
|
25
36
|
<section class="w-full flex items-center justify-center">
|
|
@@ -95,14 +106,13 @@ const imageUrl = extractImageUrl(image);
|
|
|
95
106
|
|
|
96
107
|
)}
|
|
97
108
|
|
|
98
|
-
<<<<<<< HEAD
|
|
99
109
|
<div class={`w-full lg:w-1/2 p-4 flex items-center ${orientation === 'left' ? 'justify-end' : 'justify-start'} aspect-auto`}>
|
|
100
|
-
<img src={
|
|
101
|
-
=======
|
|
102
|
-
<div class={`w-full lg:w-1/2 p-4 flex items-center ${orientation === 'left' ? 'justify-end' : 'justify-start'} aspect-square`}>
|
|
103
|
-
<img src={imageUrl} alt={title} class="w-[100%] h-[100%] object-cover rounded-lg" loading="lazy"/>
|
|
104
|
-
>>>>>>> develop
|
|
110
|
+
<img src={imageUrl} alt={title} class="max-w-xl w-[75%] h-auto object-cover rounded-lg m-auto" loading="lazy" />
|
|
105
111
|
</div>
|
|
112
|
+
|
|
113
|
+
{/* <div class={`w-full lg:w-1/2 p-4 flex items-center ${orientation === 'left' ? 'justify-end' : 'justify-start'} aspect-square`}>
|
|
114
|
+
<img src={imageSkyscraper} alt={title} class="w-[100%] h-[100%] object-cover rounded-lg" loading="lazy"/>
|
|
115
|
+
</div> */}
|
|
106
116
|
</div>
|
|
107
117
|
</article>
|
|
108
118
|
) : (
|
package/src/index.ts
CHANGED
|
@@ -127,5 +127,11 @@ export const components = {
|
|
|
127
127
|
Video_2025_Valencia: Video_2025_Valencia,
|
|
128
128
|
ReactButton: ReactButton
|
|
129
129
|
};
|
|
130
|
-
// 🆕 Exportar funciones helper
|
|
131
|
-
export {
|
|
130
|
+
// 🆕 Exportar funciones helper de imagen
|
|
131
|
+
export {
|
|
132
|
+
extractImageUrl,
|
|
133
|
+
parseImageData,
|
|
134
|
+
extractAllCrops,
|
|
135
|
+
createImageProps,
|
|
136
|
+
getCropByName
|
|
137
|
+
} from './lib/functions.js';
|
package/src/lib/functions.js
CHANGED
|
@@ -115,28 +115,28 @@ export function extractImageUrl(value, options = {}) {
|
|
|
115
115
|
*/
|
|
116
116
|
export function parseImageData(value, options = {}) {
|
|
117
117
|
const { isProd = false } = options;
|
|
118
|
-
|
|
118
|
+
|
|
119
119
|
if (!value) return { original: null, images: [], url: '' };
|
|
120
|
-
|
|
120
|
+
|
|
121
121
|
try {
|
|
122
122
|
const data = JSON.parse(decodeHtmlEntities(value));
|
|
123
|
-
|
|
123
|
+
|
|
124
124
|
const resolvedOriginal = data.original ? {
|
|
125
125
|
...data.original,
|
|
126
126
|
url: resolveUrl(data.original.url, isProd)
|
|
127
127
|
} : null;
|
|
128
|
-
|
|
128
|
+
|
|
129
129
|
const resolvedImages = (data.images || []).map(img => ({
|
|
130
130
|
...img,
|
|
131
131
|
url: resolveUrl(img.url, isProd)
|
|
132
132
|
}));
|
|
133
|
-
|
|
133
|
+
|
|
134
134
|
return {
|
|
135
135
|
original: resolvedOriginal,
|
|
136
136
|
images: resolvedImages,
|
|
137
137
|
url: resolvedImages[0]?.url || resolvedOriginal?.url || resolveUrl(data.url, isProd) || ''
|
|
138
138
|
};
|
|
139
|
-
|
|
139
|
+
|
|
140
140
|
} catch {
|
|
141
141
|
return {
|
|
142
142
|
original: null,
|
|
@@ -146,6 +146,161 @@ export function parseImageData(value, options = {}) {
|
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
// ============================================================================
|
|
150
|
+
// 🎯 Funciones para múltiples recortes (Multi-crop support)
|
|
151
|
+
// ============================================================================
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Extrae todos los recortes de una imagen en formato key-value
|
|
155
|
+
*
|
|
156
|
+
* Soporta dos formatos de entrada:
|
|
157
|
+
* 1. JSON de Limbo (desde PageBuilder):
|
|
158
|
+
* {"original":{"url":"..."}, "images":[{"name":"base","url":"..."},{"name":"sky","url":"..."}]}
|
|
159
|
+
* 2. Objeto simple (uso estático sin PageBuilder):
|
|
160
|
+
* { base: "https://...", skyscraper: "https://..." }
|
|
161
|
+
*
|
|
162
|
+
* @param {string|object} value - JSON de Limbo, URL directa, u objeto {cropName: url}
|
|
163
|
+
* @param {Object} options - { isProd: boolean, includeOriginal: boolean }
|
|
164
|
+
* @returns {Object} Objeto con crops: { base: "url", skyscraper: "url", original?: "url" }
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* // Desde PageBuilder (JSON de Limbo)
|
|
168
|
+
* const crops = extractAllCrops(image);
|
|
169
|
+
* const baseUrl = crops.base;
|
|
170
|
+
* const skyUrl = crops.skyscraper;
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* // Uso estático (objeto simple)
|
|
174
|
+
* <Componente image={{ base: "url1", skyscraper: "url2" }} />
|
|
175
|
+
* const crops = extractAllCrops(image); // Devuelve { base: "url1", skyscraper: "url2" }
|
|
176
|
+
*/
|
|
177
|
+
export function extractAllCrops(value, options = {}) {
|
|
178
|
+
const { isProd = false, includeOriginal = true } = options;
|
|
179
|
+
|
|
180
|
+
if (!value) return {};
|
|
181
|
+
|
|
182
|
+
// Caso 1: Ya es un objeto simple {key: url} - uso estático sin PageBuilder
|
|
183
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
184
|
+
// Verificar si es un objeto simple (todas las propiedades son strings URL)
|
|
185
|
+
const keys = Object.keys(value);
|
|
186
|
+
const isSimpleObject = keys.length > 0 && keys.every(key =>
|
|
187
|
+
typeof value[key] === 'string' || value[key] === null || value[key] === undefined
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
if (isSimpleObject) {
|
|
191
|
+
// Resolver URLs relativas si es necesario
|
|
192
|
+
const result = {};
|
|
193
|
+
for (const key of keys) {
|
|
194
|
+
if (value[key]) {
|
|
195
|
+
result[key] = resolveUrl(value[key], isProd);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Caso 2: Es un string - puede ser JSON de Limbo o URL directa
|
|
203
|
+
if (typeof value === 'string') {
|
|
204
|
+
const normalizedValue = decodeHtmlEntities(value);
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
const data = JSON.parse(normalizedValue);
|
|
208
|
+
const result = {};
|
|
209
|
+
|
|
210
|
+
// Extraer todos los crops del array images
|
|
211
|
+
if (data.images && Array.isArray(data.images)) {
|
|
212
|
+
for (const img of data.images) {
|
|
213
|
+
if (img && img.name && isValidImageUrl(img.url)) {
|
|
214
|
+
result[img.name] = resolveUrl(img.url, isProd);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Incluir original si se solicita
|
|
220
|
+
if (includeOriginal && data.original?.url && isValidImageUrl(data.original.url)) {
|
|
221
|
+
result.original = resolveUrl(data.original.url, isProd);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Si no hay crops pero hay URL directa en el JSON
|
|
225
|
+
if (Object.keys(result).length === 0 && data.url && isValidImageUrl(data.url)) {
|
|
226
|
+
result.default = resolveUrl(data.url, isProd);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return result;
|
|
230
|
+
|
|
231
|
+
} catch {
|
|
232
|
+
// No es JSON válido - tratar como URL directa
|
|
233
|
+
if (isValidImageUrl(normalizedValue)) {
|
|
234
|
+
return { default: resolveUrl(normalizedValue, isProd) };
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return {};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Crea el objeto de props de imagen para uso estático (sin PageBuilder)
|
|
244
|
+
*
|
|
245
|
+
* Útil cuando se usa un componente directamente desde la librería
|
|
246
|
+
* y se quiere pasar múltiples crops de forma estructurada.
|
|
247
|
+
*
|
|
248
|
+
* @param {Object} crops - Objeto con los crops: { base: "url", skyscraper: "url" }
|
|
249
|
+
* @param {string} originalUrl - URL de la imagen original (opcional)
|
|
250
|
+
* @returns {string} JSON string compatible con el formato de Limbo
|
|
251
|
+
*
|
|
252
|
+
* @example
|
|
253
|
+
* // En un archivo .astro
|
|
254
|
+
* import { createImageProps } from 'libreria-astro-lefebvre/lib/functions';
|
|
255
|
+
*
|
|
256
|
+
* <Componente
|
|
257
|
+
* image={createImageProps({
|
|
258
|
+
* base: "https://example.com/base.jpg",
|
|
259
|
+
* skyscraper: "https://example.com/sky.jpg"
|
|
260
|
+
* })}
|
|
261
|
+
* />
|
|
262
|
+
*
|
|
263
|
+
* // También se puede pasar el objeto directamente (más simple):
|
|
264
|
+
* <Componente image={{ base: "url1", skyscraper: "url2" }} />
|
|
265
|
+
*/
|
|
266
|
+
export function createImageProps(crops, originalUrl = null) {
|
|
267
|
+
if (!crops || typeof crops !== 'object') return '';
|
|
268
|
+
|
|
269
|
+
const images = Object.entries(crops).map(([name, url]) => ({
|
|
270
|
+
name,
|
|
271
|
+
url,
|
|
272
|
+
width: null,
|
|
273
|
+
height: null
|
|
274
|
+
}));
|
|
275
|
+
|
|
276
|
+
const result = {
|
|
277
|
+
original: originalUrl ? { url: originalUrl } : null,
|
|
278
|
+
images
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
return JSON.stringify(result);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Obtiene un crop específico por nombre, con fallback
|
|
286
|
+
*
|
|
287
|
+
* @param {string|object} value - JSON de Limbo u objeto simple
|
|
288
|
+
* @param {string} cropName - Nombre del crop a buscar
|
|
289
|
+
* @param {Object} options - { isProd: boolean, fallback: string }
|
|
290
|
+
* @returns {string} URL del crop o fallback
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* const baseUrl = getCropByName(image, 'base');
|
|
294
|
+
* const skyUrl = getCropByName(image, 'skyscraper', { fallback: baseUrl });
|
|
295
|
+
*/
|
|
296
|
+
export function getCropByName(value, cropName, options = {}) {
|
|
297
|
+
const { isProd = false, fallback = '' } = options;
|
|
298
|
+
|
|
299
|
+
const allCrops = extractAllCrops(value, { isProd });
|
|
300
|
+
|
|
301
|
+
return allCrops[cropName] || allCrops.original || allCrops.default || fallback;
|
|
302
|
+
}
|
|
303
|
+
|
|
149
304
|
/**
|
|
150
305
|
* Preprocesa campos de imagen para enviar a servidor de preview
|
|
151
306
|
*/
|
package/src/limbo/index.ts
CHANGED
|
@@ -12,5 +12,13 @@ export {
|
|
|
12
12
|
type LimboInitOptions
|
|
13
13
|
} from './init';
|
|
14
14
|
|
|
15
|
+
// Exportar handler para API endpoint de token
|
|
16
|
+
export {
|
|
17
|
+
createLimboTokenHandler,
|
|
18
|
+
type LimboTokenConfig,
|
|
19
|
+
type LimboTokenResponse,
|
|
20
|
+
LIMBO_ENV_VARS
|
|
21
|
+
} from './tokenHandler';
|
|
22
|
+
|
|
15
23
|
// El componente Astro se importa directamente:
|
|
16
24
|
// import LimboProvider from 'libreria-astro-lefebvre/limbo/LimboProvider.astro';
|
package/src/limbo/init.ts
CHANGED
|
@@ -114,7 +114,6 @@ export function generateLimboInitScript(config: LimboConfig): string {
|
|
|
114
114
|
var targetSelector = selector || '${selector}';
|
|
115
115
|
var inputs = document.querySelectorAll(targetSelector);
|
|
116
116
|
var processed = 0;
|
|
117
|
-
console.log('[Limbo] Rescan manual: buscando inputs con selector', targetSelector);
|
|
118
117
|
inputs.forEach(function(input) {
|
|
119
118
|
if (input.tagName === 'INPUT' && !input.dataset.limboProcessed) {
|
|
120
119
|
try {
|
|
@@ -126,7 +125,6 @@ export function generateLimboInitScript(config: LimboConfig): string {
|
|
|
126
125
|
}
|
|
127
126
|
}
|
|
128
127
|
});
|
|
129
|
-
console.log('[Limbo] Rescan completado:', processed, 'inputs procesados de', inputs.length, 'encontrados');
|
|
130
128
|
return processed;
|
|
131
129
|
};
|
|
132
130
|
|
|
@@ -219,17 +217,13 @@ export function generateLimboInitScript(config: LimboConfig): string {
|
|
|
219
217
|
|
|
220
218
|
// Debug: Escanear manualmente después de un delay para Vue/React hydration
|
|
221
219
|
setTimeout(function() {
|
|
222
|
-
console.log('[Limbo] Re-escaneando inputs después de hydration...');
|
|
223
220
|
var inputs = document.querySelectorAll('${selector}');
|
|
224
|
-
console.log('[Limbo] Encontrados ' + inputs.length + ' inputs con selector ${selector}');
|
|
225
221
|
inputs.forEach(function(input, idx) {
|
|
226
|
-
console.log('[Limbo] Input ' + idx + ':', input.tagName, input.className, input.id || input.name);
|
|
227
222
|
// Forzar procesamiento si es un input válido
|
|
228
223
|
if (input.tagName === 'INPUT' && !input.dataset.limboProcessed) {
|
|
229
224
|
try {
|
|
230
225
|
Limbo.autoInputs._processInput(input);
|
|
231
226
|
input.dataset.limboProcessed = 'true';
|
|
232
|
-
console.log('[Limbo] Input procesado manualmente:', input.id || input.name);
|
|
233
227
|
} catch (e) {
|
|
234
228
|
console.error('[Limbo] Error procesando input:', e);
|
|
235
229
|
}
|
|
@@ -244,7 +238,6 @@ export function generateLimboInitScript(config: LimboConfig): string {
|
|
|
244
238
|
return input.tagName === 'INPUT' && !input.dataset.limboProcessed;
|
|
245
239
|
});
|
|
246
240
|
if (pendingInputs.length > 0) {
|
|
247
|
-
console.log('[Limbo] Segunda pasada: procesando ' + pendingInputs.length + ' inputs pendientes');
|
|
248
241
|
pendingInputs.forEach(function(input) {
|
|
249
242
|
try {
|
|
250
243
|
Limbo.autoInputs._processInput(input);
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Limbo Token Handler
|
|
3
|
+
*
|
|
4
|
+
* Handler genérico para obtener tokens JWT de Limbo.
|
|
5
|
+
* Framework-agnostic: funciona en Astro, Next.js, Express, etc.
|
|
6
|
+
*
|
|
7
|
+
* La URL de la API de Limbo se determina automáticamente según `isProduction`:
|
|
8
|
+
* - Producción: https://limbo.lefebvre.es
|
|
9
|
+
* - Desarrollo: https://led-dev-limbo-dev.eu.els.local
|
|
10
|
+
* - Local: http://localhost:8000
|
|
11
|
+
*
|
|
12
|
+
* @example Astro
|
|
13
|
+
* ```ts
|
|
14
|
+
* // src/pages/api/limbo-token.ts
|
|
15
|
+
* import { createLimboTokenHandler } from 'libreria-astro-lefebvre/limbo';
|
|
16
|
+
*
|
|
17
|
+
* export const POST = createLimboTokenHandler({
|
|
18
|
+
* publicKey: import.meta.env.PUBLIC_LIMBO_PUBLIC_KEY,
|
|
19
|
+
* isProduction: import.meta.env.IS_PROD === 'TRUE'
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @example Next.js App Router
|
|
24
|
+
* ```ts
|
|
25
|
+
* // app/api/limbo-token/route.ts
|
|
26
|
+
* import { createLimboTokenHandler } from 'libreria-astro-lefebvre/limbo';
|
|
27
|
+
*
|
|
28
|
+
* export const POST = createLimboTokenHandler({
|
|
29
|
+
* publicKey: process.env.PUBLIC_LIMBO_PUBLIC_KEY!,
|
|
30
|
+
* isProduction: process.env.NODE_ENV === 'production'
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/** URLs por defecto de la API de Limbo */
|
|
36
|
+
const LIMBO_API_URLS = {
|
|
37
|
+
production: 'https://limbo.lefebvre.es',
|
|
38
|
+
development: 'https://led-dev-limbo-dev.eu.els.local',
|
|
39
|
+
} as const;
|
|
40
|
+
|
|
41
|
+
export interface LimboTokenConfig {
|
|
42
|
+
/** Public Key del portal de Limbo (pk_xxx) */
|
|
43
|
+
publicKey: string;
|
|
44
|
+
/** ¿Usar API de producción? (default: false = desarrollo) */
|
|
45
|
+
isProduction?: boolean;
|
|
46
|
+
/** URL base de la API de Limbo (opcional, se determina por isProduction si no se especifica) */
|
|
47
|
+
limboApiUrl?: string;
|
|
48
|
+
/** Permitir override de publicKey desde el body de la request (default: true) */
|
|
49
|
+
allowPublicKeyOverride?: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface LimboTokenResponse {
|
|
53
|
+
success: boolean;
|
|
54
|
+
token?: string;
|
|
55
|
+
expires_in?: number;
|
|
56
|
+
error?: string;
|
|
57
|
+
details?: string;
|
|
58
|
+
hint?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Crea un handler para obtener tokens JWT de Limbo.
|
|
63
|
+
*
|
|
64
|
+
* Este handler actúa como proxy para evitar problemas de CORS,
|
|
65
|
+
* haciendo la petición a Limbo desde el servidor.
|
|
66
|
+
*
|
|
67
|
+
* @param config - Configuración con publicKey e isProduction
|
|
68
|
+
* @returns Handler compatible con Astro, Next.js, y otros frameworks
|
|
69
|
+
*/
|
|
70
|
+
export function createLimboTokenHandler(config: LimboTokenConfig) {
|
|
71
|
+
const {
|
|
72
|
+
publicKey: defaultPublicKey,
|
|
73
|
+
isProduction = false,
|
|
74
|
+
limboApiUrl: customApiUrl,
|
|
75
|
+
allowPublicKeyOverride = true,
|
|
76
|
+
} = config;
|
|
77
|
+
|
|
78
|
+
// Determinar URL de la API (custom > isProduction > desarrollo)
|
|
79
|
+
const limboApiUrl = customApiUrl || (isProduction ? LIMBO_API_URLS.production : LIMBO_API_URLS.development);
|
|
80
|
+
|
|
81
|
+
// Validar configuración al crear el handler
|
|
82
|
+
if (!defaultPublicKey) {
|
|
83
|
+
console.error('[Limbo Token Handler] PUBLIC_LIMBO_PUBLIC_KEY no está configurada');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
console.log(`[Limbo Token Handler] Configurado para ${isProduction ? 'PRODUCCIÓN' : 'DESARROLLO'}: ${limboApiUrl}`);
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Handler que procesa la petición de token
|
|
90
|
+
*/
|
|
91
|
+
return async function handler({ request }: { request: Request }): Promise<Response> {
|
|
92
|
+
try {
|
|
93
|
+
// Determinar qué publicKey usar
|
|
94
|
+
let publicKey = defaultPublicKey;
|
|
95
|
+
|
|
96
|
+
// Permitir override desde el body si está habilitado
|
|
97
|
+
if (allowPublicKeyOverride) {
|
|
98
|
+
try {
|
|
99
|
+
const body = await request.json();
|
|
100
|
+
if (body.public_key) {
|
|
101
|
+
publicKey = body.public_key;
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
// Si no hay body válido, usar la del entorno
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Validar que tenemos publicKey
|
|
109
|
+
if (!publicKey) {
|
|
110
|
+
return new Response(
|
|
111
|
+
JSON.stringify({
|
|
112
|
+
success: false,
|
|
113
|
+
error: 'PUBLIC_LIMBO_PUBLIC_KEY no configurada',
|
|
114
|
+
} satisfies LimboTokenResponse),
|
|
115
|
+
{
|
|
116
|
+
status: 400,
|
|
117
|
+
headers: { 'Content-Type': 'application/json' },
|
|
118
|
+
}
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Construir URL del endpoint de token
|
|
123
|
+
const tokenUrl = `${limboApiUrl}/auth/token`;
|
|
124
|
+
console.log('[Limbo Proxy] Requesting token from:', tokenUrl);
|
|
125
|
+
console.log('[Limbo Proxy] Using public_key:', publicKey.substring(0, 10) + '...');
|
|
126
|
+
|
|
127
|
+
// Hacer petición a Limbo
|
|
128
|
+
const response = await fetch(tokenUrl, {
|
|
129
|
+
method: 'POST',
|
|
130
|
+
headers: {
|
|
131
|
+
'Content-Type': 'application/json',
|
|
132
|
+
},
|
|
133
|
+
body: JSON.stringify({
|
|
134
|
+
public_key: publicKey,
|
|
135
|
+
}),
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Manejar errores de la API
|
|
139
|
+
if (!response.ok) {
|
|
140
|
+
const errorText = await response.text();
|
|
141
|
+
console.error('[Limbo Proxy] Error response:', response.status, errorText);
|
|
142
|
+
|
|
143
|
+
return new Response(
|
|
144
|
+
JSON.stringify({
|
|
145
|
+
success: false,
|
|
146
|
+
error: `API error: ${response.status}`,
|
|
147
|
+
details: errorText,
|
|
148
|
+
} satisfies LimboTokenResponse),
|
|
149
|
+
{
|
|
150
|
+
status: response.status,
|
|
151
|
+
headers: { 'Content-Type': 'application/json' },
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Respuesta exitosa
|
|
157
|
+
const data = await response.json();
|
|
158
|
+
console.log('[Limbo Proxy] Token obtained successfully');
|
|
159
|
+
|
|
160
|
+
return new Response(JSON.stringify(data), {
|
|
161
|
+
status: 200,
|
|
162
|
+
headers: { 'Content-Type': 'application/json' },
|
|
163
|
+
});
|
|
164
|
+
} catch (error) {
|
|
165
|
+
console.error('[Limbo Proxy] Error completo:', error);
|
|
166
|
+
|
|
167
|
+
// Mensaje descriptivo para debug
|
|
168
|
+
let errorMsg = 'Unknown error';
|
|
169
|
+
if (error instanceof Error) {
|
|
170
|
+
errorMsg = error.message;
|
|
171
|
+
if (error.cause) {
|
|
172
|
+
errorMsg += ` (cause: ${JSON.stringify(error.cause)})`;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return new Response(
|
|
177
|
+
JSON.stringify({
|
|
178
|
+
success: false,
|
|
179
|
+
error: errorMsg,
|
|
180
|
+
hint: 'Verifica que la API de Limbo sea accesible desde el servidor',
|
|
181
|
+
} satisfies LimboTokenResponse),
|
|
182
|
+
{
|
|
183
|
+
status: 500,
|
|
184
|
+
headers: { 'Content-Type': 'application/json' },
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* URLs por defecto de la API de Limbo (exportadas para referencia).
|
|
193
|
+
*/
|
|
194
|
+
export { LIMBO_API_URLS };
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Helper para crear configuración desde variables de entorno comunes.
|
|
198
|
+
* Útil para documentación y ejemplos.
|
|
199
|
+
*/
|
|
200
|
+
export const LIMBO_ENV_VARS = {
|
|
201
|
+
/** Variable de entorno para la Public Key (con prefijo PUBLIC_ para cliente) */
|
|
202
|
+
PUBLIC_KEY: 'PUBLIC_LIMBO_PUBLIC_KEY',
|
|
203
|
+
/** Variable de entorno para indicar si es producción */
|
|
204
|
+
IS_PROD: 'IS_PROD',
|
|
205
|
+
} as const;
|
|
206
|
+
|
|
207
|
+
export default createLimboTokenHandler;
|