webs-sdk 0.18.40 → 0.18.41
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 +451 -451
- package/dist/components/ARFilters/ARFilters.js +54 -54
- package/dist/components/AvatarAI.js +4 -4
- package/dist/components/CookieConsent/constants.js +31 -31
- package/dist/components/CreativeFaceSwap.js +12 -12
- package/dist/components/MemeGenerator.js +12 -12
- package/dist/components/PasswordReset/PasswordResetForm.d.ts +3 -0
- package/dist/components/PasswordReset/PasswordResetForm.d.ts.map +1 -0
- package/dist/components/PasswordReset/PasswordResetForm.js +130 -0
- package/dist/components/PasswordReset/PasswordResetForm.js.map +1 -0
- package/dist/components/PasswordReset/PasswordResetRequest.d.ts +3 -0
- package/dist/components/PasswordReset/PasswordResetRequest.d.ts.map +1 -0
- package/dist/components/PasswordReset/PasswordResetRequest.js +102 -0
- package/dist/components/PasswordReset/PasswordResetRequest.js.map +1 -0
- package/dist/components/PasswordReset/constants.d.ts +6 -0
- package/dist/components/PasswordReset/constants.d.ts.map +1 -0
- package/dist/components/PasswordReset/constants.js +55 -0
- package/dist/components/PasswordReset/constants.js.map +1 -0
- package/dist/components/PasswordReset/index.d.ts +5 -0
- package/dist/components/PasswordReset/index.d.ts.map +1 -0
- package/dist/components/PasswordReset/index.js +10 -0
- package/dist/components/PasswordReset/index.js.map +1 -0
- package/dist/components/PasswordReset/translations.d.ts +3 -0
- package/dist/components/PasswordReset/translations.d.ts.map +1 -0
- package/dist/components/PasswordReset/translations.js +72 -0
- package/dist/components/PasswordReset/translations.js.map +1 -0
- package/dist/components/PasswordReset/types.d.ts +89 -0
- package/dist/components/PasswordReset/types.d.ts.map +1 -0
- package/dist/components/PasswordReset/types.js +3 -0
- package/dist/components/PasswordReset/types.js.map +1 -0
- package/dist/components/SpinningWheel/SpinningWheel.js +7 -7
- package/dist/utils/videoValidation.d.ts.map +1 -1
- package/dist/utils/videoValidation.js +59 -4
- package/dist/utils/videoValidation.js.map +1 -1
- package/package.json +78 -78
- package/public/spin2win/assets/border-afristream.svg +26 -26
- package/public/spin2win/assets/border-efc.svg +23 -23
- package/public/spin2win/assets/border-totalgym.svg +27 -27
- package/public/spin2win/assets/border-womantoday.svg +26 -26
- package/public/spin2win/assets/indicator-afristream.svg +28 -28
- package/public/spin2win/assets/indicator-efc.svg +29 -29
- package/public/spin2win/assets/indicator-totalgym.svg +29 -29
- package/public/spin2win/fonts/switzer/README.md +22 -22
- package/dist/libraries/globals.d.ts +0 -17
- package/dist/libraries/globals.d.ts.map +0 -1
- package/dist/libraries/globals.js +0 -38
- package/dist/libraries/globals.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,451 +1,451 @@
|
|
|
1
|
-
# webs-sdk
|
|
2
|
-
|
|
3
|
-
SDK interno npm para portales Nexum. Encapsula toda la lógica común: autenticación, sesiones, comunicación con la API, tracking, internacionalización y componentes React reutilizables.
|
|
4
|
-
|
|
5
|
-
**Versión actual:** 0.18.9
|
|
6
|
-
|
|
7
|
-
## Instalación
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npm install webs-sdk
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
## Uso básico
|
|
14
|
-
|
|
15
|
-
```typescript
|
|
16
|
-
import WebsSDK from 'webs-sdk';
|
|
17
|
-
|
|
18
|
-
// Inicializar sesión
|
|
19
|
-
await WebsSDK.Session.init();
|
|
20
|
-
|
|
21
|
-
// Petición HTTP
|
|
22
|
-
const data = await WebsSDK.Networking.request('/api/endpoint', { param: 'value' });
|
|
23
|
-
|
|
24
|
-
// Almacenamiento local
|
|
25
|
-
WebsSDK.Storage.set('key', 'value');
|
|
26
|
-
const value = WebsSDK.Storage.get('key');
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
## Librerías disponibles
|
|
30
|
-
|
|
31
|
-
### Networking
|
|
32
|
-
|
|
33
|
-
```typescript
|
|
34
|
-
// Petición HTTP (inyecta sessionData automáticamente)
|
|
35
|
-
await WebsSDK.Networking.request(url, data);
|
|
36
|
-
|
|
37
|
-
// Eventos de tracking
|
|
38
|
-
await WebsSDK.Networking.sendEvent('action', 'button_click', { button_id: 'submit' });
|
|
39
|
-
|
|
40
|
-
// Suscripciones
|
|
41
|
-
await WebsSDK.Networking.createSubscription(subscriptionData);
|
|
42
|
-
const { success, subscription_active } = await WebsSDK.Networking.checkSubscription();
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
### Storage
|
|
46
|
-
|
|
47
|
-
```typescript
|
|
48
|
-
WebsSDK.Storage.set('key', 'value');
|
|
49
|
-
const value = WebsSDK.Storage.get('key');
|
|
50
|
-
|
|
51
|
-
// Compresión de imágenes (0.8 ratio, 1024px max)
|
|
52
|
-
const compressed = await WebsSDK.Storage.compressImage(imageFile, 2); // 2MB max
|
|
53
|
-
|
|
54
|
-
// Creaciones (imágenes generadas por el usuario)
|
|
55
|
-
await WebsSDK.Storage.handleDownloadImageToCreations(base64, 'id', metadata);
|
|
56
|
-
const creations = await WebsSDK.Storage.getCreations();
|
|
57
|
-
await WebsSDK.Storage.deleteCreation('id');
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### Session
|
|
61
|
-
|
|
62
|
-
```typescript
|
|
63
|
-
await WebsSDK.Session.init();
|
|
64
|
-
|
|
65
|
-
const sessionData = WebsSDK.Session.getSessionData();
|
|
66
|
-
const userId = WebsSDK.Session.getUserID();
|
|
67
|
-
const lang = WebsSDK.Session.getDeviceLanguage();
|
|
68
|
-
const isSubscribed = WebsSDK.Session.getIsSubscribed();
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### Utils
|
|
72
|
-
|
|
73
|
-
```typescript
|
|
74
|
-
const id = WebsSDK.Utils.generateId();
|
|
75
|
-
const isValidEmail = WebsSDK.Utils.isValidEmail('user@example.com');
|
|
76
|
-
const isMobile = WebsSDK.Utils.isMobile();
|
|
77
|
-
const deviceInfo = WebsSDK.Utils.getDeviceInfo();
|
|
78
|
-
const truncated = WebsSDK.Utils.truncateText('Long text...', 10);
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
### MixPanel
|
|
82
|
-
|
|
83
|
-
```typescript
|
|
84
|
-
await WebsSDK.MixPanel.initialize(token, false, false, false);
|
|
85
|
-
await WebsSDK.MixPanel.trackEvent('page_view', { page: 'home' });
|
|
86
|
-
await WebsSDK.MixPanel.identifyUser('user_123');
|
|
87
|
-
await WebsSDK.MixPanel.setUserProperties({ plan: 'premium' });
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### AuthManager
|
|
91
|
-
|
|
92
|
-
```typescript
|
|
93
|
-
// Login estándar
|
|
94
|
-
await WebsSDK.AuthManager.authUser({ login, password, website_id });
|
|
95
|
-
|
|
96
|
-
// Login sin password (MSISDN)
|
|
97
|
-
await WebsSDK.AuthManager.authOnlyUser({ login, website_id });
|
|
98
|
-
|
|
99
|
-
// Login con OTP
|
|
100
|
-
await WebsSDK.AuthManager.authOnlyUserOTP({ login, website_id, pincode });
|
|
101
|
-
|
|
102
|
-
// Registro
|
|
103
|
-
await WebsSDK.AuthManager.createUser({ login, password, website_id });
|
|
104
|
-
|
|
105
|
-
// Auto-login post-pago (usado en el proxy de next_template)
|
|
106
|
-
await WebsSDK.AuthManager.auto_login(cfg_sessionid, websiteData, requestUrl);
|
|
107
|
-
|
|
108
|
-
// Baja/cancelación
|
|
109
|
-
await WebsSDK.AuthManager.unsubscribeUser({ user_id, callback_url?, org? });
|
|
110
|
-
|
|
111
|
-
// Gestión de perfil
|
|
112
|
-
await WebsSDK.AuthManager.setUserMetadata(params);
|
|
113
|
-
await WebsSDK.AuthManager.uploadProfilePhoto(params); // JPG/PNG/WEBP, max 5MB
|
|
114
|
-
await WebsSDK.AuthManager.changePassword(params);
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### Andromeda — Portales
|
|
118
|
-
|
|
119
|
-
```typescript
|
|
120
|
-
// Metadata del portal (optimizado con cookie websiteDataFlag)
|
|
121
|
-
await WebsSDK.Andromeda.getWebsiteMetadatabyHostname('portal.com');
|
|
122
|
-
|
|
123
|
-
// Configuración del producto (langs, login_type, logo, favicon, theme)
|
|
124
|
-
await WebsSDK.Andromeda.getWebsiteConfig('portal-corp-web');
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
### ContentManager
|
|
128
|
-
|
|
129
|
-
```typescript
|
|
130
|
-
// Contenido por preset
|
|
131
|
-
await WebsSDK.ContentManager.getContentByPreset('homepage', { lang: 'es' });
|
|
132
|
-
|
|
133
|
-
// Colección filtrada por tags
|
|
134
|
-
await WebsSDK.ContentManager.getCollection(['tag1', 'tag2'], 'es', false);
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
### User
|
|
138
|
-
|
|
139
|
-
```typescript
|
|
140
|
-
await WebsSDK.User.checkSession(token); // Valida token (usado en middleware)
|
|
141
|
-
await WebsSDK.User.getUserData(user_id, website_id);
|
|
142
|
-
await WebsSDK.User.getCredits(user_id, website_id);
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
### CorporateAuthManager
|
|
146
|
-
|
|
147
|
-
```typescript
|
|
148
|
-
// Server-side (middleware Next.js)
|
|
149
|
-
const result = await WebsSDK.CorporateAuthManager.validateCorporateAuthServerSide(request);
|
|
150
|
-
if (!result.authenticated) return result.redirectResponse;
|
|
151
|
-
|
|
152
|
-
// Client-side
|
|
153
|
-
await WebsSDK.CorporateAuthManager.checkCorporateLogin();
|
|
154
|
-
await WebsSDK.CorporateAuthManager.validateUser(autoRedirect?);
|
|
155
|
-
await WebsSDK.CorporateAuthManager.login(returnTo?);
|
|
156
|
-
await WebsSDK.CorporateAuthManager.logout(redirectTo?);
|
|
157
|
-
await WebsSDK.CorporateAuthManager.isAdmin(userData?);
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
### Legal
|
|
161
|
-
|
|
162
|
-
```typescript
|
|
163
|
-
await WebsSDK.Legal.getLegalText('tos' | 'privacy' | 'faq', web_id, lang);
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
### Namespace `pre` — Pre-producción
|
|
167
|
-
|
|
168
|
-
```typescript
|
|
169
|
-
// Lazy-loaded, apunta a bc1742-pre.gways.org
|
|
170
|
-
await WebsSDK.pre.AuthManager.authUser(params);
|
|
171
|
-
await WebsSDK.pre.Andromeda.getWebsiteMetadatabyHostname(hostname);
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
---
|
|
175
|
-
|
|
176
|
-
## Componentes React
|
|
177
|
-
|
|
178
|
-
### CookieConsent — Banner GDPR
|
|
179
|
-
|
|
180
|
-
```typescript
|
|
181
|
-
import { CookieConsent, getCookieConsentTexts } from 'webs-sdk';
|
|
182
|
-
import type { CookieConsentProps, CookieConsentTheme, CookieConsentPreferences } from 'webs-sdk';
|
|
183
|
-
|
|
184
|
-
<CookieConsent
|
|
185
|
-
locale="es" // en | es | fr | nl | ar | de | fi | ro | tr
|
|
186
|
-
privacyPolicyUrl="/privacy"
|
|
187
|
-
theme={{
|
|
188
|
-
backgroundColor: '#1a1a2e',
|
|
189
|
-
accentColor: '#6c63ff', // aplica a botón Accept, borde Reject, links y glow
|
|
190
|
-
acceptButtonBg: 'linear-gradient(90deg, #6c63ff, #a855f7)', // soporta gradients CSS
|
|
191
|
-
textColor: '#ffffff',
|
|
192
|
-
}}
|
|
193
|
-
onAccept={() => updateGAConsent('granted')}
|
|
194
|
-
onReject={() => updateGAConsent('denied')}
|
|
195
|
-
onSaveSettings={(prefs: CookieConsentPreferences) => handlePrefs(prefs)}
|
|
196
|
-
/>
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
**Storage:**
|
|
200
|
-
- Cookie `cookieConsent`: `accepted | rejected | custom` (1 año)
|
|
201
|
-
- `localStorage.TRACKING_PERMISSION`: `granted | denied`
|
|
202
|
-
- `localStorage.FUNCTIONAL_PERMISSION`: `granted | denied`
|
|
203
|
-
|
|
204
|
-
### Google Analytics — GA4
|
|
205
|
-
|
|
206
|
-
```typescript
|
|
207
|
-
import { initGoogleAnalytics, updateGAConsent, trackGAEvent, trackGAPageView, isGALoaded } from 'webs-sdk';
|
|
208
|
-
|
|
209
|
-
// Registra el GA ID. Carga el script SOLO si hay consentimiento previo.
|
|
210
|
-
await initGoogleAnalytics('G-XXXXXXXXXX', { sendPageView: true, debug: false });
|
|
211
|
-
|
|
212
|
-
// Llamar tras decisión del usuario en CookieConsent
|
|
213
|
-
updateGAConsent('granted'); // carga GA si había pendingTrackingId
|
|
214
|
-
updateGAConsent('denied'); // GA nunca se carga
|
|
215
|
-
|
|
216
|
-
// Tracking
|
|
217
|
-
trackGAEvent('purchase', { value: 9.99, currency: 'EUR' });
|
|
218
|
-
trackGAPageView('/home', 'Inicio');
|
|
219
|
-
isGALoaded(); // boolean
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
> **GDPR estricto:** GA no usa Consent Mode v2. Si no hay consentimiento, el script `gtag.js` nunca se inyecta en el DOM.
|
|
223
|
-
|
|
224
|
-
### CDN Proxy
|
|
225
|
-
|
|
226
|
-
```typescript
|
|
227
|
-
import { handleCDNRequest } from 'webs-sdk';
|
|
228
|
-
import type { CDNOptions } from 'webs-sdk';
|
|
229
|
-
|
|
230
|
-
// Usar en un route handler de Next.js (compatible con Edge Runtime)
|
|
231
|
-
async function handleCDNRequest(
|
|
232
|
-
pathname: string, // '/cdn/apariencias/33702/image.png'
|
|
233
|
-
options?: {
|
|
234
|
-
cdnBaseUrl?: string; // Default: 'https://dy822md8ge77v.cloudfront.net'
|
|
235
|
-
cacheSeconds?: number; // Default: 300 (5 min)
|
|
236
|
-
}
|
|
237
|
-
): Promise<Response | null> // null si pathname no empieza por /cdn/
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
### Componentes "For You" — Creación de contenido AI
|
|
241
|
-
|
|
242
|
-
```typescript
|
|
243
|
-
import {
|
|
244
|
-
AvatarAI, AvatarAIPage, AvatarAIForYouCard,
|
|
245
|
-
CreativeFaceSwap, CreativeFaceSwapPage, CreativeFaceSwapForYouCard,
|
|
246
|
-
MemeGenerator, MemeGeneratorPage, MemeGeneratorForYouCard,
|
|
247
|
-
WallpapersName, WallpapersNamePage, WallpapersNameForYouCard,
|
|
248
|
-
Wallpapers,
|
|
249
|
-
Ringtone,
|
|
250
|
-
} from 'webs-sdk';
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
Todos aceptan `websiteId?: string` para tracking multi-portal.
|
|
254
|
-
|
|
255
|
-
### ARFilters — Filtros AR con DeepAR
|
|
256
|
-
|
|
257
|
-
```typescript
|
|
258
|
-
import { ARFilters, ARFiltersForYouCard } from 'webs-sdk';
|
|
259
|
-
import type { DeepARProps, DeepARFilter, DeepARTexts } from 'webs-sdk';
|
|
260
|
-
|
|
261
|
-
<ARFilters
|
|
262
|
-
licenseKey="deepar-license-key"
|
|
263
|
-
websiteId="portal-web-id"
|
|
264
|
-
apiBaseUrl="https://api.fluver-ai.com"
|
|
265
|
-
downloadProxyUrl="/cdn" // Proxy CORS para descarga de efectos
|
|
266
|
-
filterTag="ar-filters-tag" // Obtiene filtros del CMS por tag
|
|
267
|
-
// O bien: filters={[{ file: 'beauty', thumbUrl: '/thumb.jpg' }]}
|
|
268
|
-
locale="es"
|
|
269
|
-
texts={getARFiltersTexts('es')} // requerido
|
|
270
|
-
onPhotoTaken={(url) => share(url)}
|
|
271
|
-
onVideoRecorded={(url) => share(url)}
|
|
272
|
-
/>
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
Soporta fullscreen en mobile (vendor-prefixed API para iOS Safari).
|
|
276
|
-
|
|
277
|
-
### SpinningWheel — Ruleta
|
|
278
|
-
|
|
279
|
-
```typescript
|
|
280
|
-
import { SpinningWheel, wheelThemes } from 'webs-sdk';
|
|
281
|
-
|
|
282
|
-
// Temas: vodacom | totalgym | womantoday | afristream | efc
|
|
283
|
-
<SpinningWheel
|
|
284
|
-
userId={userId}
|
|
285
|
-
websiteId={websiteId}
|
|
286
|
-
theme="vodacom"
|
|
287
|
-
onResult={(segment) => handleWin(segment)}
|
|
288
|
-
canSpin={true}
|
|
289
|
-
alreadySpunToday={false}
|
|
290
|
-
/>
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
### Quiz y Stickers
|
|
294
|
-
|
|
295
|
-
```typescript
|
|
296
|
-
import { Quiz, Stickers } from 'webs-sdk';
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
### Esports — Noticias, Vídeos y Partidas en Directo
|
|
300
|
-
|
|
301
|
-
```typescript
|
|
302
|
-
import { EsportsNews, EsportsVideos, EsportsLive } from 'webs-sdk';
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
#### EsportsNews
|
|
306
|
-
|
|
307
|
-
```typescript
|
|
308
|
-
<EsportsNews
|
|
309
|
-
config={{
|
|
310
|
-
key: 'api-key',
|
|
311
|
-
contents: [{ preset: 'esports_news', apiProviderId: 'id', category: [], tags: [], limit: 10 }]
|
|
312
|
-
}}
|
|
313
|
-
language="es"
|
|
314
|
-
onNewsClick={(item) => router.push(`/news/${item.id}`)}
|
|
315
|
-
theme={theme} // CSS classes para container, cards, badges, etc.
|
|
316
|
-
categoryStyles={{ gaming: { badge: 'bg-blue-500', badgeText: 'text-white' } }}
|
|
317
|
-
// Modo detalle (renderiza EsportsNewsDetail):
|
|
318
|
-
newsId="article-id"
|
|
319
|
-
newsData={item} // opcional si ya tienes los datos
|
|
320
|
-
onBack={() => router.back()}
|
|
321
|
-
/>
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
#### EsportsVideos
|
|
325
|
-
|
|
326
|
-
```typescript
|
|
327
|
-
<EsportsVideos
|
|
328
|
-
config={{ key: 'api-key', contents: [...] }}
|
|
329
|
-
language="es"
|
|
330
|
-
onVideoClick={(item) => openPlayer(item.video_url)}
|
|
331
|
-
theme={theme} // incluye clases para duration overlay
|
|
332
|
-
/>
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
#### EsportsLive
|
|
336
|
-
|
|
337
|
-
```typescript
|
|
338
|
-
<EsportsLive
|
|
339
|
-
language="es"
|
|
340
|
-
timezone="Europe/Madrid"
|
|
341
|
-
onLiveClick={(item) => openStream(item.streamUrl)}
|
|
342
|
-
onMatchClick={(match) => openMatchDetail(match)}
|
|
343
|
-
onGameClick={(gameId) => filterByGame(gameId)}
|
|
344
|
-
website_id="portal-web-id"
|
|
345
|
-
theme={theme}
|
|
346
|
-
// Modo detalle de partida:
|
|
347
|
-
matchId="serie-id"
|
|
348
|
-
matchData={calendarMatch}
|
|
349
|
-
onBack={() => setMatchId(null)}
|
|
350
|
-
/>
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
---
|
|
354
|
-
|
|
355
|
-
## Utilidades de validación de vídeo
|
|
356
|
-
|
|
357
|
-
```typescript
|
|
358
|
-
import { validateVideoUrl, validateVideoBatch, clearValidationCache } from 'webs-sdk';
|
|
359
|
-
import { useValidateVideoOnMount, useLazyVideoValidation } from 'webs-sdk';
|
|
360
|
-
|
|
361
|
-
// Función pura (sin React)
|
|
362
|
-
const state = await validateVideoUrl('https://stream.example.com/live.m3u8');
|
|
363
|
-
// 'valid' | 'invalid' | 'unknown'
|
|
364
|
-
|
|
365
|
-
// Batch con concurrencia 3
|
|
366
|
-
const results = await validateVideoBatch([url1, url2, url3]);
|
|
367
|
-
|
|
368
|
-
// React: validar al montar (evita renderizar streams rotos)
|
|
369
|
-
const { isValid, isInvalid, isValidating } = useValidateVideoOnMount(videoUrl);
|
|
370
|
-
|
|
371
|
-
// React: validación manual
|
|
372
|
-
const { validate, isValid, isInvalid } = useLazyVideoValidation();
|
|
373
|
-
await validate(videoUrl);
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
Soporta HLS, DASH, MP4. Cache en memoria con TTL de 5 minutos.
|
|
377
|
-
|
|
378
|
-
---
|
|
379
|
-
|
|
380
|
-
## Estructura del proyecto
|
|
381
|
-
|
|
382
|
-
```
|
|
383
|
-
src/
|
|
384
|
-
├── config.ts # Configuración central (URLs, endpoints, constantes)
|
|
385
|
-
├── index.ts # Barrel export principal
|
|
386
|
-
├── libraries/
|
|
387
|
-
│ ├── networking.ts # HTTP + events
|
|
388
|
-
│ ├── auth.ts # Autenticación, login, baja
|
|
389
|
-
│ ├── session.ts # Sesiones y dispositivo
|
|
390
|
-
│ ├── storage.ts # LocalStorage + compresión de imágenes
|
|
391
|
-
│ ├── utils.ts # Utilidades generales
|
|
392
|
-
│ ├── andromeda.ts # Metadata y config de portales
|
|
393
|
-
│ ├── corp_auth.ts # Auth corporativa (JWT)
|
|
394
|
-
│ ├── user.ts # Datos de usuario
|
|
395
|
-
│ ├── mixpanel.ts # Analytics
|
|
396
|
-
│ ├── audio.ts # Text-to-speech
|
|
397
|
-
│ ├── calypso.ts # Event tracking interno
|
|
398
|
-
│ ├── legal.ts # Textos legales
|
|
399
|
-
│ ├── content.ts # Contenido dinámico + colecciones por tags
|
|
400
|
-
│ ├── cdn.ts # CDN Proxy (Edge Runtime compatible)
|
|
401
|
-
│ └── googleAnalytics.ts # GA4 con carga diferida por consentimiento
|
|
402
|
-
├── utils/
|
|
403
|
-
│ ├── videoValidation.ts # Validación de URLs de streaming
|
|
404
|
-
│ ├── useVideoValidation.ts # React hooks para validación
|
|
405
|
-
│ └── timeUtils.ts # getTimeAgo — tiempo relativo i18n
|
|
406
|
-
└── components/
|
|
407
|
-
├── AvatarAI.tsx
|
|
408
|
-
├── CreativeFaceSwap.tsx
|
|
409
|
-
├── Wallpapers.tsx
|
|
410
|
-
├── WallpapersName.tsx
|
|
411
|
-
├── Ringtone.tsx
|
|
412
|
-
├── MemeGenerator.tsx
|
|
413
|
-
├── Quiz.tsx
|
|
414
|
-
├── Stickers.tsx
|
|
415
|
-
├── VideoPlayer.tsx
|
|
416
|
-
├── ARFilters/
|
|
417
|
-
├── SpinningWheel/
|
|
418
|
-
├── CookieConsent/
|
|
419
|
-
└── esports/
|
|
420
|
-
├── news/
|
|
421
|
-
├── videos/
|
|
422
|
-
└── live/
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
## Desarrollo
|
|
426
|
-
|
|
427
|
-
```bash
|
|
428
|
-
npm install
|
|
429
|
-
npm run build # tsc → dist/
|
|
430
|
-
npm run build:watch # watch mode
|
|
431
|
-
npm run lint
|
|
432
|
-
```
|
|
433
|
-
|
|
434
|
-
## Publicación
|
|
435
|
-
|
|
436
|
-
```bash
|
|
437
|
-
# Incluir [ROAD-361723] en el commit para auto-publish en CI
|
|
438
|
-
git commit -m "feat: descripción [ROAD-361723]"
|
|
439
|
-
git push
|
|
440
|
-
```
|
|
441
|
-
|
|
442
|
-
Tras publicar, actualizar en los portales:
|
|
443
|
-
```bash
|
|
444
|
-
npm install webs-sdk@<nueva-version>
|
|
445
|
-
```
|
|
446
|
-
|
|
447
|
-
**Nota importante — `index.js` raíz:** es el barrel CommonJS mantenido manualmente. Cada export nuevo en `src/index.ts` debe añadirse también en `index.js`:
|
|
448
|
-
```js
|
|
449
|
-
const { nuevaFuncion } = require('./dist/index.js');
|
|
450
|
-
module.exports.nuevaFuncion = nuevaFuncion;
|
|
451
|
-
```
|
|
1
|
+
# webs-sdk
|
|
2
|
+
|
|
3
|
+
SDK interno npm para portales Nexum. Encapsula toda la lógica común: autenticación, sesiones, comunicación con la API, tracking, internacionalización y componentes React reutilizables.
|
|
4
|
+
|
|
5
|
+
**Versión actual:** 0.18.9
|
|
6
|
+
|
|
7
|
+
## Instalación
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install webs-sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Uso básico
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import WebsSDK from 'webs-sdk';
|
|
17
|
+
|
|
18
|
+
// Inicializar sesión
|
|
19
|
+
await WebsSDK.Session.init();
|
|
20
|
+
|
|
21
|
+
// Petición HTTP
|
|
22
|
+
const data = await WebsSDK.Networking.request('/api/endpoint', { param: 'value' });
|
|
23
|
+
|
|
24
|
+
// Almacenamiento local
|
|
25
|
+
WebsSDK.Storage.set('key', 'value');
|
|
26
|
+
const value = WebsSDK.Storage.get('key');
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Librerías disponibles
|
|
30
|
+
|
|
31
|
+
### Networking
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// Petición HTTP (inyecta sessionData automáticamente)
|
|
35
|
+
await WebsSDK.Networking.request(url, data);
|
|
36
|
+
|
|
37
|
+
// Eventos de tracking
|
|
38
|
+
await WebsSDK.Networking.sendEvent('action', 'button_click', { button_id: 'submit' });
|
|
39
|
+
|
|
40
|
+
// Suscripciones
|
|
41
|
+
await WebsSDK.Networking.createSubscription(subscriptionData);
|
|
42
|
+
const { success, subscription_active } = await WebsSDK.Networking.checkSubscription();
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Storage
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
WebsSDK.Storage.set('key', 'value');
|
|
49
|
+
const value = WebsSDK.Storage.get('key');
|
|
50
|
+
|
|
51
|
+
// Compresión de imágenes (0.8 ratio, 1024px max)
|
|
52
|
+
const compressed = await WebsSDK.Storage.compressImage(imageFile, 2); // 2MB max
|
|
53
|
+
|
|
54
|
+
// Creaciones (imágenes generadas por el usuario)
|
|
55
|
+
await WebsSDK.Storage.handleDownloadImageToCreations(base64, 'id', metadata);
|
|
56
|
+
const creations = await WebsSDK.Storage.getCreations();
|
|
57
|
+
await WebsSDK.Storage.deleteCreation('id');
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Session
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
await WebsSDK.Session.init();
|
|
64
|
+
|
|
65
|
+
const sessionData = WebsSDK.Session.getSessionData();
|
|
66
|
+
const userId = WebsSDK.Session.getUserID();
|
|
67
|
+
const lang = WebsSDK.Session.getDeviceLanguage();
|
|
68
|
+
const isSubscribed = WebsSDK.Session.getIsSubscribed();
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Utils
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
const id = WebsSDK.Utils.generateId();
|
|
75
|
+
const isValidEmail = WebsSDK.Utils.isValidEmail('user@example.com');
|
|
76
|
+
const isMobile = WebsSDK.Utils.isMobile();
|
|
77
|
+
const deviceInfo = WebsSDK.Utils.getDeviceInfo();
|
|
78
|
+
const truncated = WebsSDK.Utils.truncateText('Long text...', 10);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### MixPanel
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
await WebsSDK.MixPanel.initialize(token, false, false, false);
|
|
85
|
+
await WebsSDK.MixPanel.trackEvent('page_view', { page: 'home' });
|
|
86
|
+
await WebsSDK.MixPanel.identifyUser('user_123');
|
|
87
|
+
await WebsSDK.MixPanel.setUserProperties({ plan: 'premium' });
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### AuthManager
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// Login estándar
|
|
94
|
+
await WebsSDK.AuthManager.authUser({ login, password, website_id });
|
|
95
|
+
|
|
96
|
+
// Login sin password (MSISDN)
|
|
97
|
+
await WebsSDK.AuthManager.authOnlyUser({ login, website_id });
|
|
98
|
+
|
|
99
|
+
// Login con OTP
|
|
100
|
+
await WebsSDK.AuthManager.authOnlyUserOTP({ login, website_id, pincode });
|
|
101
|
+
|
|
102
|
+
// Registro
|
|
103
|
+
await WebsSDK.AuthManager.createUser({ login, password, website_id });
|
|
104
|
+
|
|
105
|
+
// Auto-login post-pago (usado en el proxy de next_template)
|
|
106
|
+
await WebsSDK.AuthManager.auto_login(cfg_sessionid, websiteData, requestUrl);
|
|
107
|
+
|
|
108
|
+
// Baja/cancelación
|
|
109
|
+
await WebsSDK.AuthManager.unsubscribeUser({ user_id, callback_url?, org? });
|
|
110
|
+
|
|
111
|
+
// Gestión de perfil
|
|
112
|
+
await WebsSDK.AuthManager.setUserMetadata(params);
|
|
113
|
+
await WebsSDK.AuthManager.uploadProfilePhoto(params); // JPG/PNG/WEBP, max 5MB
|
|
114
|
+
await WebsSDK.AuthManager.changePassword(params);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Andromeda — Portales
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
// Metadata del portal (optimizado con cookie websiteDataFlag)
|
|
121
|
+
await WebsSDK.Andromeda.getWebsiteMetadatabyHostname('portal.com');
|
|
122
|
+
|
|
123
|
+
// Configuración del producto (langs, login_type, logo, favicon, theme)
|
|
124
|
+
await WebsSDK.Andromeda.getWebsiteConfig('portal-corp-web');
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### ContentManager
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
// Contenido por preset
|
|
131
|
+
await WebsSDK.ContentManager.getContentByPreset('homepage', { lang: 'es' });
|
|
132
|
+
|
|
133
|
+
// Colección filtrada por tags
|
|
134
|
+
await WebsSDK.ContentManager.getCollection(['tag1', 'tag2'], 'es', false);
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### User
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
await WebsSDK.User.checkSession(token); // Valida token (usado en middleware)
|
|
141
|
+
await WebsSDK.User.getUserData(user_id, website_id);
|
|
142
|
+
await WebsSDK.User.getCredits(user_id, website_id);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### CorporateAuthManager
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
// Server-side (middleware Next.js)
|
|
149
|
+
const result = await WebsSDK.CorporateAuthManager.validateCorporateAuthServerSide(request);
|
|
150
|
+
if (!result.authenticated) return result.redirectResponse;
|
|
151
|
+
|
|
152
|
+
// Client-side
|
|
153
|
+
await WebsSDK.CorporateAuthManager.checkCorporateLogin();
|
|
154
|
+
await WebsSDK.CorporateAuthManager.validateUser(autoRedirect?);
|
|
155
|
+
await WebsSDK.CorporateAuthManager.login(returnTo?);
|
|
156
|
+
await WebsSDK.CorporateAuthManager.logout(redirectTo?);
|
|
157
|
+
await WebsSDK.CorporateAuthManager.isAdmin(userData?);
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Legal
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
await WebsSDK.Legal.getLegalText('tos' | 'privacy' | 'faq', web_id, lang);
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Namespace `pre` — Pre-producción
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
// Lazy-loaded, apunta a bc1742-pre.gways.org
|
|
170
|
+
await WebsSDK.pre.AuthManager.authUser(params);
|
|
171
|
+
await WebsSDK.pre.Andromeda.getWebsiteMetadatabyHostname(hostname);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Componentes React
|
|
177
|
+
|
|
178
|
+
### CookieConsent — Banner GDPR
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import { CookieConsent, getCookieConsentTexts } from 'webs-sdk';
|
|
182
|
+
import type { CookieConsentProps, CookieConsentTheme, CookieConsentPreferences } from 'webs-sdk';
|
|
183
|
+
|
|
184
|
+
<CookieConsent
|
|
185
|
+
locale="es" // en | es | fr | nl | ar | de | fi | ro | tr
|
|
186
|
+
privacyPolicyUrl="/privacy"
|
|
187
|
+
theme={{
|
|
188
|
+
backgroundColor: '#1a1a2e',
|
|
189
|
+
accentColor: '#6c63ff', // aplica a botón Accept, borde Reject, links y glow
|
|
190
|
+
acceptButtonBg: 'linear-gradient(90deg, #6c63ff, #a855f7)', // soporta gradients CSS
|
|
191
|
+
textColor: '#ffffff',
|
|
192
|
+
}}
|
|
193
|
+
onAccept={() => updateGAConsent('granted')}
|
|
194
|
+
onReject={() => updateGAConsent('denied')}
|
|
195
|
+
onSaveSettings={(prefs: CookieConsentPreferences) => handlePrefs(prefs)}
|
|
196
|
+
/>
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Storage:**
|
|
200
|
+
- Cookie `cookieConsent`: `accepted | rejected | custom` (1 año)
|
|
201
|
+
- `localStorage.TRACKING_PERMISSION`: `granted | denied`
|
|
202
|
+
- `localStorage.FUNCTIONAL_PERMISSION`: `granted | denied`
|
|
203
|
+
|
|
204
|
+
### Google Analytics — GA4
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
import { initGoogleAnalytics, updateGAConsent, trackGAEvent, trackGAPageView, isGALoaded } from 'webs-sdk';
|
|
208
|
+
|
|
209
|
+
// Registra el GA ID. Carga el script SOLO si hay consentimiento previo.
|
|
210
|
+
await initGoogleAnalytics('G-XXXXXXXXXX', { sendPageView: true, debug: false });
|
|
211
|
+
|
|
212
|
+
// Llamar tras decisión del usuario en CookieConsent
|
|
213
|
+
updateGAConsent('granted'); // carga GA si había pendingTrackingId
|
|
214
|
+
updateGAConsent('denied'); // GA nunca se carga
|
|
215
|
+
|
|
216
|
+
// Tracking
|
|
217
|
+
trackGAEvent('purchase', { value: 9.99, currency: 'EUR' });
|
|
218
|
+
trackGAPageView('/home', 'Inicio');
|
|
219
|
+
isGALoaded(); // boolean
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
> **GDPR estricto:** GA no usa Consent Mode v2. Si no hay consentimiento, el script `gtag.js` nunca se inyecta en el DOM.
|
|
223
|
+
|
|
224
|
+
### CDN Proxy
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
import { handleCDNRequest } from 'webs-sdk';
|
|
228
|
+
import type { CDNOptions } from 'webs-sdk';
|
|
229
|
+
|
|
230
|
+
// Usar en un route handler de Next.js (compatible con Edge Runtime)
|
|
231
|
+
async function handleCDNRequest(
|
|
232
|
+
pathname: string, // '/cdn/apariencias/33702/image.png'
|
|
233
|
+
options?: {
|
|
234
|
+
cdnBaseUrl?: string; // Default: 'https://dy822md8ge77v.cloudfront.net'
|
|
235
|
+
cacheSeconds?: number; // Default: 300 (5 min)
|
|
236
|
+
}
|
|
237
|
+
): Promise<Response | null> // null si pathname no empieza por /cdn/
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Componentes "For You" — Creación de contenido AI
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
import {
|
|
244
|
+
AvatarAI, AvatarAIPage, AvatarAIForYouCard,
|
|
245
|
+
CreativeFaceSwap, CreativeFaceSwapPage, CreativeFaceSwapForYouCard,
|
|
246
|
+
MemeGenerator, MemeGeneratorPage, MemeGeneratorForYouCard,
|
|
247
|
+
WallpapersName, WallpapersNamePage, WallpapersNameForYouCard,
|
|
248
|
+
Wallpapers,
|
|
249
|
+
Ringtone,
|
|
250
|
+
} from 'webs-sdk';
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Todos aceptan `websiteId?: string` para tracking multi-portal.
|
|
254
|
+
|
|
255
|
+
### ARFilters — Filtros AR con DeepAR
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
import { ARFilters, ARFiltersForYouCard } from 'webs-sdk';
|
|
259
|
+
import type { DeepARProps, DeepARFilter, DeepARTexts } from 'webs-sdk';
|
|
260
|
+
|
|
261
|
+
<ARFilters
|
|
262
|
+
licenseKey="deepar-license-key"
|
|
263
|
+
websiteId="portal-web-id"
|
|
264
|
+
apiBaseUrl="https://api.fluver-ai.com"
|
|
265
|
+
downloadProxyUrl="/cdn" // Proxy CORS para descarga de efectos
|
|
266
|
+
filterTag="ar-filters-tag" // Obtiene filtros del CMS por tag
|
|
267
|
+
// O bien: filters={[{ file: 'beauty', thumbUrl: '/thumb.jpg' }]}
|
|
268
|
+
locale="es"
|
|
269
|
+
texts={getARFiltersTexts('es')} // requerido
|
|
270
|
+
onPhotoTaken={(url) => share(url)}
|
|
271
|
+
onVideoRecorded={(url) => share(url)}
|
|
272
|
+
/>
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Soporta fullscreen en mobile (vendor-prefixed API para iOS Safari).
|
|
276
|
+
|
|
277
|
+
### SpinningWheel — Ruleta
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
import { SpinningWheel, wheelThemes } from 'webs-sdk';
|
|
281
|
+
|
|
282
|
+
// Temas: vodacom | totalgym | womantoday | afristream | efc
|
|
283
|
+
<SpinningWheel
|
|
284
|
+
userId={userId}
|
|
285
|
+
websiteId={websiteId}
|
|
286
|
+
theme="vodacom"
|
|
287
|
+
onResult={(segment) => handleWin(segment)}
|
|
288
|
+
canSpin={true}
|
|
289
|
+
alreadySpunToday={false}
|
|
290
|
+
/>
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Quiz y Stickers
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
import { Quiz, Stickers } from 'webs-sdk';
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Esports — Noticias, Vídeos y Partidas en Directo
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
import { EsportsNews, EsportsVideos, EsportsLive } from 'webs-sdk';
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
#### EsportsNews
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
<EsportsNews
|
|
309
|
+
config={{
|
|
310
|
+
key: 'api-key',
|
|
311
|
+
contents: [{ preset: 'esports_news', apiProviderId: 'id', category: [], tags: [], limit: 10 }]
|
|
312
|
+
}}
|
|
313
|
+
language="es"
|
|
314
|
+
onNewsClick={(item) => router.push(`/news/${item.id}`)}
|
|
315
|
+
theme={theme} // CSS classes para container, cards, badges, etc.
|
|
316
|
+
categoryStyles={{ gaming: { badge: 'bg-blue-500', badgeText: 'text-white' } }}
|
|
317
|
+
// Modo detalle (renderiza EsportsNewsDetail):
|
|
318
|
+
newsId="article-id"
|
|
319
|
+
newsData={item} // opcional si ya tienes los datos
|
|
320
|
+
onBack={() => router.back()}
|
|
321
|
+
/>
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
#### EsportsVideos
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
<EsportsVideos
|
|
328
|
+
config={{ key: 'api-key', contents: [...] }}
|
|
329
|
+
language="es"
|
|
330
|
+
onVideoClick={(item) => openPlayer(item.video_url)}
|
|
331
|
+
theme={theme} // incluye clases para duration overlay
|
|
332
|
+
/>
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
#### EsportsLive
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
<EsportsLive
|
|
339
|
+
language="es"
|
|
340
|
+
timezone="Europe/Madrid"
|
|
341
|
+
onLiveClick={(item) => openStream(item.streamUrl)}
|
|
342
|
+
onMatchClick={(match) => openMatchDetail(match)}
|
|
343
|
+
onGameClick={(gameId) => filterByGame(gameId)}
|
|
344
|
+
website_id="portal-web-id"
|
|
345
|
+
theme={theme}
|
|
346
|
+
// Modo detalle de partida:
|
|
347
|
+
matchId="serie-id"
|
|
348
|
+
matchData={calendarMatch}
|
|
349
|
+
onBack={() => setMatchId(null)}
|
|
350
|
+
/>
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
## Utilidades de validación de vídeo
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
import { validateVideoUrl, validateVideoBatch, clearValidationCache } from 'webs-sdk';
|
|
359
|
+
import { useValidateVideoOnMount, useLazyVideoValidation } from 'webs-sdk';
|
|
360
|
+
|
|
361
|
+
// Función pura (sin React)
|
|
362
|
+
const state = await validateVideoUrl('https://stream.example.com/live.m3u8');
|
|
363
|
+
// 'valid' | 'invalid' | 'unknown'
|
|
364
|
+
|
|
365
|
+
// Batch con concurrencia 3
|
|
366
|
+
const results = await validateVideoBatch([url1, url2, url3]);
|
|
367
|
+
|
|
368
|
+
// React: validar al montar (evita renderizar streams rotos)
|
|
369
|
+
const { isValid, isInvalid, isValidating } = useValidateVideoOnMount(videoUrl);
|
|
370
|
+
|
|
371
|
+
// React: validación manual
|
|
372
|
+
const { validate, isValid, isInvalid } = useLazyVideoValidation();
|
|
373
|
+
await validate(videoUrl);
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Soporta HLS, DASH, MP4. Cache en memoria con TTL de 5 minutos.
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## Estructura del proyecto
|
|
381
|
+
|
|
382
|
+
```
|
|
383
|
+
src/
|
|
384
|
+
├── config.ts # Configuración central (URLs, endpoints, constantes)
|
|
385
|
+
├── index.ts # Barrel export principal
|
|
386
|
+
├── libraries/
|
|
387
|
+
│ ├── networking.ts # HTTP + events
|
|
388
|
+
│ ├── auth.ts # Autenticación, login, baja
|
|
389
|
+
│ ├── session.ts # Sesiones y dispositivo
|
|
390
|
+
│ ├── storage.ts # LocalStorage + compresión de imágenes
|
|
391
|
+
│ ├── utils.ts # Utilidades generales
|
|
392
|
+
│ ├── andromeda.ts # Metadata y config de portales
|
|
393
|
+
│ ├── corp_auth.ts # Auth corporativa (JWT)
|
|
394
|
+
│ ├── user.ts # Datos de usuario
|
|
395
|
+
│ ├── mixpanel.ts # Analytics
|
|
396
|
+
│ ├── audio.ts # Text-to-speech
|
|
397
|
+
│ ├── calypso.ts # Event tracking interno
|
|
398
|
+
│ ├── legal.ts # Textos legales
|
|
399
|
+
│ ├── content.ts # Contenido dinámico + colecciones por tags
|
|
400
|
+
│ ├── cdn.ts # CDN Proxy (Edge Runtime compatible)
|
|
401
|
+
│ └── googleAnalytics.ts # GA4 con carga diferida por consentimiento
|
|
402
|
+
├── utils/
|
|
403
|
+
│ ├── videoValidation.ts # Validación de URLs de streaming
|
|
404
|
+
│ ├── useVideoValidation.ts # React hooks para validación
|
|
405
|
+
│ └── timeUtils.ts # getTimeAgo — tiempo relativo i18n
|
|
406
|
+
└── components/
|
|
407
|
+
├── AvatarAI.tsx
|
|
408
|
+
├── CreativeFaceSwap.tsx
|
|
409
|
+
├── Wallpapers.tsx
|
|
410
|
+
├── WallpapersName.tsx
|
|
411
|
+
├── Ringtone.tsx
|
|
412
|
+
├── MemeGenerator.tsx
|
|
413
|
+
├── Quiz.tsx
|
|
414
|
+
├── Stickers.tsx
|
|
415
|
+
├── VideoPlayer.tsx
|
|
416
|
+
├── ARFilters/
|
|
417
|
+
├── SpinningWheel/
|
|
418
|
+
├── CookieConsent/
|
|
419
|
+
└── esports/
|
|
420
|
+
├── news/
|
|
421
|
+
├── videos/
|
|
422
|
+
└── live/
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
## Desarrollo
|
|
426
|
+
|
|
427
|
+
```bash
|
|
428
|
+
npm install
|
|
429
|
+
npm run build # tsc → dist/
|
|
430
|
+
npm run build:watch # watch mode
|
|
431
|
+
npm run lint
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
## Publicación
|
|
435
|
+
|
|
436
|
+
```bash
|
|
437
|
+
# Incluir [ROAD-361723] en el commit para auto-publish en CI
|
|
438
|
+
git commit -m "feat: descripción [ROAD-361723]"
|
|
439
|
+
git push
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
Tras publicar, actualizar en los portales:
|
|
443
|
+
```bash
|
|
444
|
+
npm install webs-sdk@<nueva-version>
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
**Nota importante — `index.js` raíz:** es el barrel CommonJS mantenido manualmente. Cada export nuevo en `src/index.ts` debe añadirse también en `index.js`:
|
|
448
|
+
```js
|
|
449
|
+
const { nuevaFuncion } = require('./dist/index.js');
|
|
450
|
+
module.exports.nuevaFuncion = nuevaFuncion;
|
|
451
|
+
```
|