webs-sdk 0.19.64 → 0.19.65

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.
Files changed (100) hide show
  1. package/AGENTS.md +44 -0
  2. package/CLAUDE.md +80 -0
  3. package/README.md +513 -490
  4. package/dist/components/ARFilters/ARFilters.js +54 -54
  5. package/dist/components/ArtGenerator.js +141 -141
  6. package/dist/components/AvatarAI.js +14 -14
  7. package/dist/components/CookieConsent/constants.js +31 -31
  8. package/dist/components/CreativeFaceSwap.js +23 -23
  9. package/dist/components/GifsIa.js +76 -76
  10. package/dist/components/MemeGenerator.js +12 -12
  11. package/dist/components/PasswordReset/constants.js +33 -33
  12. package/dist/components/RecoveryPassword/constants.js +27 -27
  13. package/dist/components/SpinningWheel/SpinningWheel.js +7 -7
  14. package/dist/components/SpinningWheel/index.d.ts +1 -1
  15. package/dist/components/SpinningWheel/index.d.ts.map +1 -1
  16. package/dist/components/SpinningWheel/index.js +2 -1
  17. package/dist/components/SpinningWheel/index.js.map +1 -1
  18. package/dist/components/ui/ConfiguredMediaPlayer.d.ts +49 -0
  19. package/dist/components/ui/ConfiguredMediaPlayer.d.ts.map +1 -0
  20. package/dist/components/ui/ConfiguredMediaPlayer.js +111 -0
  21. package/dist/components/ui/ConfiguredMediaPlayer.js.map +1 -0
  22. package/dist/components/ui/badge.d.ts +1 -0
  23. package/dist/components/ui/badge.d.ts.map +1 -1
  24. package/dist/components/ui/badge.js +1 -1
  25. package/dist/components/ui/badge.js.map +1 -1
  26. package/dist/components/ui/compose-refs.d.ts +6 -0
  27. package/dist/components/ui/compose-refs.d.ts.map +1 -0
  28. package/dist/components/ui/compose-refs.js +57 -0
  29. package/dist/components/ui/compose-refs.js.map +1 -0
  30. package/dist/components/ui/dropdown-menu.d.ts +3 -1
  31. package/dist/components/ui/dropdown-menu.d.ts.map +1 -1
  32. package/dist/components/ui/dropdown-menu.js +2 -2
  33. package/dist/components/ui/dropdown-menu.js.map +1 -1
  34. package/dist/components/ui/index.d.ts +2 -0
  35. package/dist/components/ui/index.d.ts.map +1 -1
  36. package/dist/components/ui/index.js +2 -0
  37. package/dist/components/ui/index.js.map +1 -1
  38. package/dist/components/ui/media-player.d.ts +127 -0
  39. package/dist/components/ui/media-player.d.ts.map +1 -0
  40. package/dist/components/ui/media-player.js +1633 -0
  41. package/dist/components/ui/media-player.js.map +1 -0
  42. package/dist/components/ui/tooltip.d.ts +3 -1
  43. package/dist/components/ui/tooltip.d.ts.map +1 -1
  44. package/dist/components/ui/tooltip.js +2 -2
  45. package/dist/components/ui/tooltip.js.map +1 -1
  46. package/dist/components/ui/use-lazy-ref.d.ts +4 -0
  47. package/dist/components/ui/use-lazy-ref.d.ts.map +1 -0
  48. package/dist/components/ui/use-lazy-ref.js +45 -0
  49. package/dist/components/ui/use-lazy-ref.js.map +1 -0
  50. package/dist/index.d.ts +1 -1
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +4 -3
  53. package/dist/index.js.map +1 -1
  54. package/dist/network.d.ts +12 -0
  55. package/dist/network.d.ts.map +1 -0
  56. package/dist/network.js +15 -0
  57. package/dist/network.js.map +1 -0
  58. package/dist/server.d.ts +54 -0
  59. package/dist/server.d.ts.map +1 -0
  60. package/dist/server.js +74 -0
  61. package/dist/server.js.map +1 -0
  62. package/dist/types/media-chrome-store-types.d.ts +107 -0
  63. package/dist/types/media-chrome-store-types.d.ts.map +1 -0
  64. package/dist/types/media-chrome-store-types.js +3 -0
  65. package/dist/types/media-chrome-store-types.js.map +1 -0
  66. package/dist/utils/QRCode.d.ts +1 -0
  67. package/dist/utils/QRCode.d.ts.map +1 -0
  68. package/dist/utils/QRCode.js +2 -0
  69. package/dist/utils/QRCode.js.map +1 -0
  70. package/dist/utils/imageValidation.d.ts +6 -0
  71. package/dist/utils/imageValidation.d.ts.map +1 -0
  72. package/dist/utils/imageValidation.js +64 -0
  73. package/dist/utils/imageValidation.js.map +1 -0
  74. package/docs/CONFIGURED_MEDIA_PLAYER.md +40 -0
  75. package/docs/CORP_AUTH_USAGE.md +687 -0
  76. package/docs/EXAMPLES_WALLPAPERSNAME_IMPLEMENTATION.md +1445 -0
  77. package/docs/EXAMPLE_ENDPOINTS_COMUNICATION.md +55 -0
  78. package/docs/EXTERNAL_SDK_GUIDE.md +1340 -0
  79. package/docs/FOR_YOU_360_FACESWAP_FIX.md +400 -0
  80. package/docs/FOR_YOU_IMPLEMENTATION_GUIDE.md +1413 -0
  81. package/docs/FOR_YOU_QUIZ_ARCHITECTURE.md +753 -0
  82. package/docs/FOR_YOU_SDK_WALLPAPERSNAME_BUG.md +106 -0
  83. package/docs/FOR_YOU_WALLPAPERSNAME_ENHANCEMENT_PLAN.md +210 -0
  84. package/docs/LEGAL_USAGE.md +190 -0
  85. package/docs/STREAK_IMPLEMENTATION_GUIDE.md +430 -0
  86. package/package.json +106 -84
  87. package/public/spin2win/assets/border-afristream.svg +26 -26
  88. package/public/spin2win/assets/border-efc.svg +23 -23
  89. package/public/spin2win/assets/border-streetreel.svg +23 -23
  90. package/public/spin2win/assets/border-totalgym.svg +27 -27
  91. package/public/spin2win/assets/border-womantoday.svg +26 -26
  92. package/public/spin2win/assets/indicator-afristream.svg +28 -28
  93. package/public/spin2win/assets/indicator-efc.svg +29 -29
  94. package/public/spin2win/assets/indicator-streetreel.svg +29 -29
  95. package/public/spin2win/assets/indicator-totalgym.svg +29 -29
  96. package/public/spin2win/fonts/switzer/README.md +22 -22
  97. package/scripts/install-agent-skill.cjs +34 -0
  98. package/skills/webs-sdk-package/SKILL.md +11 -0
  99. package/skills/webs-sdk-package/references/components.md +30 -0
  100. package/skills/webs-sdk-package/references/integration.md +32 -0
@@ -0,0 +1,1340 @@
1
+ # webs-sdk — External Developer Guide
2
+
3
+ > **Version:** 0.18.35
4
+ > **Companion document:** [next_template External Guide](../../next_template/docs/EXTERNAL_TEMPLATE_GUIDE.md)
5
+
6
+ ---
7
+
8
+ ## Table of Contents
9
+
10
+ 1. [Introduction](#1-introduction)
11
+ 2. [Installation & Requirements](#2-installation--requirements)
12
+ 3. [SDK Architecture](#3-sdk-architecture)
13
+ 4. [Core Libraries](#4-core-libraries)
14
+ - 4.1 [AuthManager](#41-authmanager)
15
+ - 4.2 [Andromeda — Portal Metadata](#42-andromeda--portal-metadata)
16
+ - 4.3 [User](#43-user)
17
+ - 4.4 [Networking](#44-networking)
18
+ - 4.5 [Calypso — Event Tracking](#45-calypso--event-tracking)
19
+ - 4.6 [Legal](#46-legal)
20
+ - 4.7 [Session](#47-session)
21
+ - 4.8 [Storage](#48-storage)
22
+ - 4.9 [Other Libraries](#49-other-libraries)
23
+ 5. [Google Analytics](#5-google-analytics)
24
+ 6. [CookieConsent Component](#6-cookieconsent-component)
25
+ 7. [CDN Proxy](#7-cdn-proxy)
26
+ 8. [React Content Components](#8-react-content-components)
27
+ 9. [Esports Components](#9-esports-components)
28
+ 10. [UI Component Library](#10-ui-component-library)
29
+ 11. [Video Validation Utilities](#11-video-validation-utilities)
30
+ 12. [TypeScript Types Reference](#12-typescript-types-reference)
31
+ 13. [Production vs Pre-production](#13-production-vs-pre-production)
32
+ 14. [Error Patterns & Security](#14-error-patterns--security)
33
+
34
+ ---
35
+
36
+ ## 1. Introduction
37
+
38
+ `webs-sdk` is an internal npm package that centralises all shared functionality across Nexum content portals. It provides:
39
+
40
+ - **Authentication** — login, registration, auto-login from payment callbacks, subscription cancellation
41
+ - **Portal configuration** — per-hostname metadata resolution via the Andromeda backend
42
+ - **Analytics** — GDPR-compliant Google Analytics 4 integration, internal Calypso tracking, Mixpanel
43
+ - **React components** — GDPR consent banner, AI content generation widgets, esports modules, gamification
44
+ - **Utilities** — CDN proxy, video validation, localStorage wrapper, image compression
45
+
46
+ **How it fits with next_template:**
47
+ The SDK runs exclusively on the **server side** (Next.js middleware and API routes) via the BFF (Backend For Frontend) pattern. Client-side React components in `next_template` access SDK functionality through Zustand stores and internal API routes — they never import `WebsSDK` directly. See the [next_template guide](../../next_template/docs/EXTERNAL_TEMPLATE_GUIDE.md) for the full integration pattern.
48
+
49
+ ---
50
+
51
+ ## 2. Installation & Requirements
52
+
53
+ ```bash
54
+ npm install webs-sdk
55
+ ```
56
+
57
+ > This is a private npm package. You need access to the company npm registry to install it.
58
+
59
+ **Peer dependencies:**
60
+
61
+ | Package | Version |
62
+ |---|---|
63
+ | `react` | `^18.0.0 \|\| ^19.0.0` |
64
+ | `react-dom` | `^18.0.0 \|\| ^19.0.0` |
65
+ | `lucide-react` | `>=1.14.0` |
66
+
67
+ **Import pattern:**
68
+
69
+ ```typescript
70
+ // Default import — the main SDK singleton object
71
+ import WebsSDK from 'webs-sdk';
72
+
73
+ // Named imports — individual components and utilities
74
+ import { CookieConsent, initGoogleAnalytics, handleCDNRequest } from 'webs-sdk';
75
+
76
+ // UI sub-package
77
+ import { Button, Input, Dialog } from 'webs-sdk/ui';
78
+ ```
79
+
80
+ > **Warning:** Do **not** import `WebsSDK` (the default export) inside `'use client'` components. It is designed for server-side use (API routes, middleware). Access SDK data on the client through Zustand stores and fetch calls to your own API routes.
81
+
82
+ ---
83
+
84
+ ## 3. SDK Architecture
85
+
86
+ ```
87
+ WebsSDK (singleton object)
88
+ ├── Networking — HTTP layer, /config init, event queue
89
+ ├── AuthManager — Authentication, auto-login, unsubscribe
90
+ ├── Andromeda — Portal metadata by hostname
91
+ ├── User — Session validation, subscription check
92
+ ├── Calypso — Internal event tracking
93
+ ├── Legal — Legal document retrieval
94
+ ├── Session — Session lifecycle
95
+ ├── Storage — localStorage wrapper
96
+ ├── ContentManager — Content fetching by preset / tag collection
97
+ ├── MixPanel — External analytics
98
+ ├── Audio — Text-to-speech
99
+ ├── I18nManager — Internationalisation (placeholder)
100
+ ├── Utils — General utilities
101
+ ├── Config — Mutable configuration object
102
+ └── .pre (lazy) — Pre-production instances of the above
103
+ ├── AuthManager → bc1742-pre.gways.org
104
+ ├── User
105
+ ├── Andromeda
106
+ └── Calypso
107
+
108
+ Standalone exports (not on the WebsSDK object):
109
+ ├── CookieConsent — React component
110
+ ├── initGoogleAnalytics — GA4 with consent gate
111
+ ├── handleCDNRequest — CloudFront proxy (Edge-compatible)
112
+ └── validateVideoUrl — Video URL validation
113
+ ```
114
+
115
+ **Singleton pattern:** All library classes are instantiated once at module load. Do not instantiate them yourself — always use `WebsSDK.<Library>`.
116
+
117
+ **Mutable config:** `WebsSDK.Config` starts with static defaults. `Networking.executeInit()` fetches `/config` from the backend and overwrites endpoint URLs, events, and feature flags at runtime. If you call SDK methods before `executeInit()` runs, they will use the static defaults.
118
+
119
+ **Pre-production namespace:** `WebsSDK.pre` is lazy-loaded on first access. In `next_template`, all API routes use `WebsSDK.pre.*` to target the pre-production backend (`bc1742-pre.gways.org`).
120
+
121
+ ---
122
+
123
+ ## 4. Core Libraries
124
+
125
+ ### 4.1 AuthManager
126
+
127
+ **File:** `src/libraries/auth.ts`
128
+ **Accessed as:** `WebsSDK.AuthManager` (production) or `WebsSDK.pre.AuthManager` (pre-production)
129
+
130
+ #### `authUser(params)` — Standard login
131
+
132
+ ```typescript
133
+ const result = await WebsSDK.pre.AuthManager.authUser({
134
+ login: 'user@example.com',
135
+ password: 'secret123',
136
+ website_id: 'myportal-web',
137
+ });
138
+ ```
139
+
140
+ | Parameter | Type | Required | Description |
141
+ |---|---|---|---|
142
+ | `login` | `string` | Yes | Email or username |
143
+ | `password` | `string` | Yes | Plain text password |
144
+ | `website_id` | `string` | Yes | Portal identifier from Andromeda |
145
+
146
+ **Returns:** `AuthResult`
147
+
148
+ ```typescript
149
+ // Success
150
+ { status: 'success', data: { success: 1, us_id, us_login, session_id, metadata, subscription_data, ... } }
151
+
152
+ // Error
153
+ { status: 'error', errorKey: 'invalidCredentials', error: 'Authentication error' }
154
+ ```
155
+
156
+ **Error keys:**
157
+
158
+ | `errorKey` | Meaning |
159
+ |---|---|
160
+ | `userNotFound` | No user with that login |
161
+ | `invalidCredentials` | Wrong password |
162
+ | `accountDisabled` | Account is disabled |
163
+ | `userNotActive` | Account not yet activated |
164
+ | `authFailed` | Generic authentication failure |
165
+ | `serverError` | Backend unreachable |
166
+ | `invalidData` | Missing required fields |
167
+
168
+ ---
169
+
170
+ #### `authOnlyUser(params)` — Login without password (MSISDN)
171
+
172
+ Used when the login type is phone-number-based (no password required).
173
+
174
+ ```typescript
175
+ const result = await WebsSDK.pre.AuthManager.authOnlyUser({
176
+ login: '+34612345678',
177
+ website_id: 'myportal-web',
178
+ login_only_user: 'msisdn',
179
+ });
180
+ ```
181
+
182
+ ---
183
+
184
+ #### `authOnlyUserOTP(params)` — Login with OTP
185
+
186
+ Validates the one-time PIN sent by SMS.
187
+
188
+ ```typescript
189
+ const result = await WebsSDK.pre.AuthManager.authOnlyUserOTP({
190
+ login: '+34612345678',
191
+ website_id: 'myportal-web',
192
+ pincode: '123456',
193
+ });
194
+ ```
195
+
196
+ ---
197
+
198
+ #### `createUser(params)` — Registration
199
+
200
+ ```typescript
201
+ const result = await WebsSDK.pre.AuthManager.createUser({
202
+ login: 'user@example.com',
203
+ password: 'secret123',
204
+ website_id: 'myportal-web',
205
+ });
206
+ ```
207
+
208
+ **Error keys:** `userAlreadyExists`, `signupFailed`, `serverError`
209
+
210
+ ---
211
+
212
+ #### `auto_login(cfg_sessionid, websiteData, requestUrl)` — Post-payment auto-login
213
+
214
+ > **Critical:** This method is called exclusively inside the Next.js middleware (`proxy.ts`) when the user arrives from a payment gateway with `?cfg_sessionid=` in the URL.
215
+
216
+ ```typescript
217
+ const result = await WebsSDK.AuthManager.auto_login(
218
+ cfg_sessionid, // String from payment gateway URL param
219
+ websiteData, // Object from Andromeda.getWebsiteMetadatabyHostname()
220
+ url.toString() // Full request URL (used to strip the param)
221
+ );
222
+ // result.status: 'success' | 'error'
223
+ // result.redirectUrl: URL without cfg_sessionid
224
+ // result.cookies: Array of CookieToSet — apply to the NextResponse
225
+ ```
226
+
227
+ **Flow:**
228
+ 1. Calls `getThankyouPageMetadata(cfg_sessionid)` → extracts user credentials from the payment session
229
+ 2. Calls `authUser()` with those credentials
230
+ 3. Returns `cookies` array containing `autoLoginFlag` (TTL: 10 seconds)
231
+ 4. Sets `wip: true` on the user data if the session is recent (< 1 hour) and no active subscription yet
232
+
233
+ The middleware strips `cfg_sessionid` from the URL and redirects. The client-side `AuthGuard` detects `autoLoginFlag` and hydrates the auth store.
234
+
235
+ ---
236
+
237
+ #### `unsubscribeUser(params)` — Cancel subscription
238
+
239
+ ```typescript
240
+ const result = await WebsSDK.pre.AuthManager.unsubscribeUser({
241
+ user_id: '12345',
242
+ callback_url: 'https://yourportal.com/unsubscribe-callback', // optional
243
+ org: 'myorg', // optional
244
+ });
245
+ ```
246
+
247
+ | Parameter | Type | Required | Description |
248
+ |---|---|---|---|
249
+ | `user_id` | `string` | Yes | User ID |
250
+ | `callback_url` | `string` | No | Notification URL after cancellation |
251
+ | `org` | `string` | No | Organisation identifier |
252
+
253
+ **Error keys:** `unsubscribeFailed`, `serverError`, `invalidData`
254
+ **Note:** Error key `114` means "no active subscriptions found".
255
+
256
+ ---
257
+
258
+ #### `setUserMetadata(params)` — Update profile metadata
259
+
260
+ ```typescript
261
+ const result = await WebsSDK.pre.AuthManager.setUserMetadata({
262
+ login: 'user@example.com',
263
+ password: 'secret123', // optional
264
+ website_id: 'myportal-web',
265
+ display_name: 'John Doe', // any metadata fields
266
+ });
267
+ ```
268
+
269
+ ---
270
+
271
+ #### `uploadProfilePhoto(params)` — Upload avatar
272
+
273
+ ```typescript
274
+ const result = await WebsSDK.pre.AuthManager.uploadProfilePhoto({
275
+ login: 'user@example.com',
276
+ website_id: 'myportal-web',
277
+ resource: file, // File object, max 5 MB, JPG/PNG/WEBP only
278
+ });
279
+ // result.profile_photo — URL of the uploaded photo
280
+ ```
281
+
282
+ **Validation:** max 5 MB, accepted types: `image/jpeg`, `image/jpg`, `image/png`, `image/webp`
283
+ **Error keys:** `invalidFormat`, `fileTooLarge`, `uploadFailed`
284
+
285
+ ---
286
+
287
+ #### `changePassword(params)`
288
+
289
+ ```typescript
290
+ await WebsSDK.pre.AuthManager.changePassword({
291
+ login: 'user@example.com',
292
+ website_id: 'myportal-web',
293
+ new_pass: 'newSecret123',
294
+ repeat_new_pass: 'newSecret123',
295
+ });
296
+ ```
297
+
298
+ ---
299
+
300
+ #### `changeCredentials(params)`
301
+
302
+ Changes both login and password in one call.
303
+
304
+ ```typescript
305
+ await WebsSDK.pre.AuthManager.changeCredentials({
306
+ login: 'old@example.com',
307
+ password: 'oldSecret',
308
+ website_id: 'myportal-web',
309
+ new_login: 'new@example.com',
310
+ new_pass: 'newSecret123',
311
+ repeat_new_pass: 'newSecret123',
312
+ });
313
+ ```
314
+
315
+ ---
316
+
317
+ #### `getThankyouPageMetadata(cfg_sessionid)`
318
+
319
+ Retrieves user credentials from a payment session token. Called internally by `auto_login`. Can be used directly on the thank-you page.
320
+
321
+ ```typescript
322
+ const result = await WebsSDK.AuthManager.getThankyouPageMetadata(cfg_sessionid);
323
+ // result.data: { user: { login, password }, website: { lang }, channel: { id, mccmnc } }
324
+ ```
325
+
326
+ ---
327
+
328
+ #### `logout()` / `hasActiveSession()`
329
+
330
+ ```typescript
331
+ WebsSDK.AuthManager.logout(); // Clears local session markers
332
+ WebsSDK.AuthManager.hasActiveSession(); // Returns boolean
333
+ ```
334
+
335
+ > In `next_template`, session teardown also requires a `POST /api/auth/logout` call to clear the `userSession` httpOnly cookie.
336
+
337
+ ---
338
+
339
+ ### 4.2 Andromeda — Portal Metadata
340
+
341
+ **File:** `src/libraries/andromeda.ts`
342
+ **Accessed as:** `WebsSDK.Andromeda` (production) or `WebsSDK.pre.Andromeda`
343
+
344
+ This library resolves a domain hostname into portal configuration. It is the entry point for the multi-tenant system.
345
+
346
+ #### `getWebsiteMetadatabyHostname(hostname, existingCookie?)`
347
+
348
+ ```typescript
349
+ const result = await WebsSDK.Andromeda.getWebsiteMetadatabyHostname('musikgen.com');
350
+ // result.status: 'success' | 'error'
351
+ // result.websiteData: WebsiteData object
352
+ // result.cookies: Array<CookieToSet> — set these on the middleware response
353
+ ```
354
+
355
+ The method checks for a cached `websiteDataFlag` cookie first. If the cookie exists and matches the hostname, it skips the backend fetch. Otherwise it fetches from the backend and returns a `cookies` array with a new `websiteDataFlag` cookie (TTL: 1 hour).
356
+
357
+ **`WebsiteData` structure:**
358
+
359
+ ```typescript
360
+ interface WebsiteData {
361
+ web_id: string; // Unique portal identifier — e.g. 'musikgen-corp-web'
362
+ web_pais: string; // Country code
363
+ web_estado: string; // Portal status in Argo
364
+ web_collection: string; // Andromeda collection
365
+ web_version: number; // Portal version
366
+ web_url: string; // Canonical URL
367
+ web_analytics_id: string | null; // GA4 tracking ID (G-XXXXXXXX)
368
+ hostname?: string; // Resolved hostname (added by SDK)
369
+ custom_operators: string | null;
370
+ web_category: string;
371
+ web_andro_template: string | null;
372
+ }
373
+ ```
374
+
375
+ ---
376
+
377
+ #### `getWebsiteConfig(web_id, merchantId?)`
378
+
379
+ Returns the full product and feature configuration for a portal.
380
+
381
+ ```typescript
382
+ const result = await WebsSDK.Andromeda.getWebsiteConfig('musikgen-corp-web');
383
+ // result.data: { product_config, features_config }
384
+ ```
385
+
386
+ **`product_config` structure:**
387
+
388
+ ```typescript
389
+ interface ProductConfig {
390
+ website_id: string;
391
+ title: string; // Portal display name
392
+ langs: string[]; // Enabled languages — e.g. ['en', 'es', 'fr']
393
+ default_lang: string; // Default language — e.g. 'en'
394
+ login_type: string; // Login method — 'email', 'msisdn', etc.
395
+ login_only_user: string | boolean;
396
+ on_premium_redirect: string; // Redirect path after subscription
397
+ logo: string; // Logo URL
398
+ favicon: string; // Favicon URL
399
+ footer: string[]; // Footer link identifiers
400
+ account: { manage_subscription: boolean };
401
+ skip_signup: boolean;
402
+ shortcuts_dropdownmenu: string[];
403
+ navigation_session?: boolean;// If true, middleware validates session on every request
404
+ check_thankyou_metadata_default_lang?: boolean;
405
+ // Cookie banner colours (optional — used by CookieConsentWrapper)
406
+ cookieBannerBgColor?: string;
407
+ cookieBannerTextColor?: string;
408
+ cookieBannerAccentColor?: string;
409
+ privacyPolicyUrl?: string;
410
+ }
411
+ ```
412
+
413
+ **`features_config` structure:**
414
+
415
+ ```typescript
416
+ interface FeaturesConfig {
417
+ limits: {
418
+ credit_period: string;
419
+ credit_limit: {
420
+ subscribed_user: any;
421
+ free_user: any;
422
+ };
423
+ };
424
+ features_disabled: string[];
425
+ file_sizes: { image: number; pdf: number; video_min: number };
426
+ }
427
+ ```
428
+
429
+ ---
430
+
431
+ ### 4.3 User
432
+
433
+ **File:** `src/libraries/user.ts`
434
+ **Accessed as:** `WebsSDK.User` (production) or `WebsSDK.pre.User`
435
+
436
+ #### `checkSession(params)` — Validate session token
437
+
438
+ Used by the middleware when `product_config.navigation_session === true`.
439
+
440
+ ```typescript
441
+ const result = await WebsSDK.User.checkSession({
442
+ user_id: '12345',
443
+ session: 'session-token-from-cookie',
444
+ });
445
+ // result.data.valid: boolean
446
+ ```
447
+
448
+ If `valid === false`, the middleware clears the `userSession` cookie and redirects to `/login`.
449
+
450
+ ---
451
+
452
+ #### `checkUserSubscription(params)`
453
+
454
+ ```typescript
455
+ const result = await WebsSDK.User.checkUserSubscription({ userId: '12345' });
456
+ ```
457
+
458
+ ---
459
+
460
+ #### `checkUserLinkedStatus(params)`
461
+
462
+ ```typescript
463
+ const result = await WebsSDK.User.checkUserLinkedStatus({ userId: '12345' });
464
+ ```
465
+
466
+ ---
467
+
468
+ #### `getUserCredits(params)`
469
+
470
+ ```typescript
471
+ const result = await WebsSDK.User.getUserCredits({
472
+ userId: '12345',
473
+ website_id: 'myportal-web',
474
+ });
475
+ ```
476
+
477
+ ---
478
+
479
+ #### `getAlias(params)`
480
+
481
+ ```typescript
482
+ const result = await WebsSDK.User.getAlias({ userId: '12345' });
483
+ ```
484
+
485
+ ---
486
+
487
+ ### 4.4 Networking
488
+
489
+ **File:** `src/libraries/networking.ts`
490
+ **Accessed as:** `WebsSDK.Networking`
491
+
492
+ The HTTP layer used internally by all other libraries. All requests are `POST` with JSON body.
493
+
494
+ #### `request(url, data)` — Generic HTTP call
495
+
496
+ ```typescript
497
+ const result = await WebsSDK.Networking.request(
498
+ 'https://bc1742.gways.org/user/get',
499
+ { user_id: '12345' }
500
+ );
501
+ ```
502
+
503
+ **Automatic behaviour:**
504
+ - Injects session data from the `userSession` cookie (via `next/headers` when in request scope)
505
+ - Adds `x-nexum-session-id` header if a session token is found
506
+ - Supports `FormData` (sets `multipart/form-data`)
507
+ - Logs performance timing
508
+
509
+ ---
510
+
511
+ #### `executeInit()` — Bootstrap SDK config
512
+
513
+ Fetches `/config` from the backend and mutates `WebsSDK.Config` with live endpoints, event identifiers, paywall data, and image compression settings.
514
+
515
+ ```typescript
516
+ await WebsSDK.Networking.executeInit();
517
+ ```
518
+
519
+ This is called internally by `Session.init()`. You only need to call it manually if you are not using the session init flow.
520
+
521
+ ---
522
+
523
+ #### `sendEvent(eventType, eventKeyword, eventData?, forceSend?)`
524
+
525
+ ```typescript
526
+ await WebsSDK.Networking.sendEvent(
527
+ 'action', // eventType: 'other' | 'action' | 'screen'
528
+ 'button_click', // eventKeyword: must match a key in config.EVENTS
529
+ { variant: 'cta' }, // optional payload
530
+ false // forceSend: bypass special-event dedup logic
531
+ );
532
+ ```
533
+
534
+ Special events (`first_open`, `subscription_purchased`, `paywall_shown`, `tutorial_completed`) are sent only once and stored in localStorage.
535
+
536
+ ---
537
+
538
+ #### `createSubscription(data)` / `checkSubscription()`
539
+
540
+ ```typescript
541
+ await WebsSDK.Networking.createSubscription({ ... });
542
+ const isActive = await WebsSDK.Networking.checkSubscription(); // returns boolean
543
+ ```
544
+
545
+ ---
546
+
547
+ #### Pending events queue
548
+
549
+ For offline scenarios:
550
+
551
+ ```typescript
552
+ WebsSDK.Networking.addPendingEvent({ eventType, eventKeyword, eventData });
553
+ await WebsSDK.Networking.sendPendingEvents(); // flush when back online
554
+ WebsSDK.Networking.clearPendingEvents();
555
+ ```
556
+
557
+ ---
558
+
559
+ ### 4.5 Calypso — Event Tracking
560
+
561
+ **File:** `src/libraries/calypso.ts`
562
+ **Accessed as:** `WebsSDK.Calypso` or `WebsSDK.pre.Calypso`
563
+
564
+ Calypso is the internal analytics platform. It is separate from Mixpanel (which is a third-party service).
565
+
566
+ ```typescript
567
+ await WebsSDK.Calypso.trackEvent(
568
+ 'page_view', // event_name
569
+ 'screen', // event_type: 'screen' | 'action' | 'other'
570
+ { // event_data: any payload
571
+ url: '/home',
572
+ is_logged: true,
573
+ user_id: '12345',
574
+ }
575
+ );
576
+ ```
577
+
578
+ In `next_template`, Calypso is called from `POST /api/calypso/event_track`. The hook `useEventTracker()` builds the payload (user metadata, URL, device info) and calls that endpoint.
579
+
580
+ ---
581
+
582
+ ### 4.6 Legal
583
+
584
+ **File:** `src/libraries/legal.ts`
585
+ **Accessed as:** `WebsSDK.Legal`
586
+
587
+ ```typescript
588
+ const result = await WebsSDK.Legal.getLegalText(
589
+ 'myportal-web', // website_id
590
+ 'tos', // type: 'tos' | 'privacy' | 'cookies' | 'faq'
591
+ 'en' // language (optional, defaults to portal default)
592
+ );
593
+ // result.data: HTML string of the legal document
594
+ ```
595
+
596
+ ---
597
+
598
+ ### 4.7 Session
599
+
600
+ **File:** `src/libraries/session.ts`
601
+ **Accessed as:** `WebsSDK.Session`
602
+
603
+ Handles the full SDK initialisation lifecycle.
604
+
605
+ ```typescript
606
+ await WebsSDK.Session.init();
607
+ // Sequence:
608
+ // 1. storeSessionStructure() — browser/device info
609
+ // 2. checkUserID() — fetch or create anonymous user ID
610
+ // 3. Networking.executeInit() — fetch /config and mutate Config
611
+ // 4. checkFirstOpen() — fire first_open event if new user
612
+ ```
613
+
614
+ Other useful methods:
615
+
616
+ ```typescript
617
+ WebsSDK.Session.getSessionData(); // Returns SessionData: { device, adjust, lang, package }
618
+ WebsSDK.Session.getSessionID(); // Returns string | null
619
+ WebsSDK.Session.getUserID(); // Returns string | null
620
+ WebsSDK.Session.getIsSubscribed(); // Returns boolean
621
+ WebsSDK.Session.getIsFirstOpen(); // Returns boolean
622
+ await WebsSDK.Session.setIsSubscribed(true);
623
+ ```
624
+
625
+ ---
626
+
627
+ ### 4.8 Storage
628
+
629
+ **File:** `src/libraries/storage.ts`
630
+ **Accessed as:** `WebsSDK.Storage`
631
+
632
+ A localStorage wrapper that also handles image operations.
633
+
634
+ ```typescript
635
+ // Basic key-value
636
+ WebsSDK.Storage.set('myKey', 'myValue');
637
+ const value = WebsSDK.Storage.get('myKey'); // string | null
638
+ WebsSDK.Storage.remove('myKey');
639
+ WebsSDK.Storage.has('myKey'); // boolean
640
+ WebsSDK.Storage.clear();
641
+
642
+ // Async variants (for complex objects)
643
+ await WebsSDK.Storage.storeData('myKey', { foo: 'bar' });
644
+ const data = await WebsSDK.Storage.getData('myKey');
645
+
646
+ // Image compression
647
+ const base64 = await WebsSDK.Storage.compressImage(imageUri, 5); // max 5 MB
648
+
649
+ // User-generated creations
650
+ await WebsSDK.Storage.handleDownloadImageToCreations(base64, 'avatar.jpg', metadata);
651
+ const creations = await WebsSDK.Storage.getCreations();
652
+ await WebsSDK.Storage.deleteCreation('dirName');
653
+ await WebsSDK.Storage.deleteAllCreations();
654
+ ```
655
+
656
+ ---
657
+
658
+ ### 4.9 Other Libraries
659
+
660
+ #### Utils
661
+
662
+ ```typescript
663
+ import WebsSDK from 'webs-sdk';
664
+ import { cn } from 'webs-sdk'; // Tailwind class merge helper
665
+
666
+ WebsSDK.Utils.isMobile() // boolean
667
+ WebsSDK.Utils.isBase64(str) // boolean
668
+ WebsSDK.Utils.generateId() // string (random UUID)
669
+ WebsSDK.Utils.debounce(fn, 300) // debounced function
670
+ WebsSDK.Utils.isValidEmail(str) // boolean
671
+ WebsSDK.Utils.buildQueryString({}) // string
672
+ await WebsSDK.Utils.encryptData(str) // { encryptedData, ivBase64 }
673
+ ```
674
+
675
+ #### Audio (Text-to-Speech)
676
+
677
+ ```typescript
678
+ await WebsSDK.Audio.speakText('Hello world', 'en');
679
+ WebsSDK.Audio.stop();
680
+ WebsSDK.Audio.isSupported(); // boolean
681
+ ```
682
+
683
+ #### MixPanel
684
+
685
+ ```typescript
686
+ await WebsSDK.MixPanel.initialize(token, trackAutomaticEvents, devMode);
687
+ await WebsSDK.MixPanel.trackEvent('purchase', { amount: 9.99 });
688
+ await WebsSDK.MixPanel.superProperties({ user_type: 'subscribed' });
689
+ ```
690
+
691
+ #### ContentManager
692
+
693
+ ```typescript
694
+ // Fetch content by CMS preset
695
+ const result = await WebsSDK.ContentManager.getContentByPreset('homepage', { lang: 'en' });
696
+
697
+ // Fetch a tagged content collection (for carousels, filtered lists)
698
+ const collection = await WebsSDK.ContentManager.getCollection(
699
+ ['esports', 'featured'], // tags
700
+ 'en', // language
701
+ false // preprod flag
702
+ );
703
+ ```
704
+
705
+ ---
706
+
707
+ ## 5. Google Analytics
708
+
709
+ **File:** `src/libraries/googleAnalytics.ts`
710
+
711
+ GA4 is **never loaded** until the user explicitly grants consent. There is no Consent Mode v2 fallback — if there is no consent, the script is never injected.
712
+
713
+ ```typescript
714
+ import {
715
+ initGoogleAnalytics,
716
+ updateGAConsent,
717
+ trackGAEvent,
718
+ trackGAPageView,
719
+ isGALoaded,
720
+ } from 'webs-sdk';
721
+ import type { GAOptions } from 'webs-sdk';
722
+ ```
723
+
724
+ #### `initGoogleAnalytics(trackingId, options?)`
725
+
726
+ Register the GA4 tracking ID. Loads the script immediately if consent was already granted; otherwise stores the ID and waits for `updateGAConsent('granted')`.
727
+
728
+ ```typescript
729
+ await initGoogleAnalytics('G-XXXXXXXXXX', {
730
+ sendPageView: true, // send page_view on init (default: true)
731
+ debug: false, // enable GA4 debug_mode (default: false)
732
+ });
733
+ ```
734
+
735
+ **Load flow:**
736
+ ```
737
+ initGoogleAnalytics(gaId)
738
+ ├── localStorage.TRACKING_PERMISSION === 'granted'
739
+ │ → inject <script>, call gtag('config', gaId)
740
+ └── no consent yet
741
+ → store as _pendingTrackingId (waits)
742
+
743
+ updateGAConsent('granted')
744
+ ├── _pendingTrackingId exists → inject script now
745
+ └── GA already loaded → gtag('consent', 'update', { analytics_storage: 'granted' })
746
+
747
+ updateGAConsent('denied')
748
+ └── localStorage.TRACKING_PERMISSION = 'denied' (GA never loads)
749
+ ```
750
+
751
+ #### `updateGAConsent(status)` — Call from consent callbacks
752
+
753
+ ```typescript
754
+ // On accept all
755
+ updateGAConsent('granted');
756
+
757
+ // On reject / essential only
758
+ updateGAConsent('denied');
759
+ ```
760
+
761
+ #### `trackGAEvent(eventName, params?)`
762
+
763
+ ```typescript
764
+ trackGAEvent('purchase', { value: 9.99, currency: 'EUR' });
765
+ // No-op if GA is not loaded
766
+ ```
767
+
768
+ #### `trackGAPageView(url?, title?)`
769
+
770
+ ```typescript
771
+ trackGAPageView('/new-page', 'New Page Title');
772
+ ```
773
+
774
+ #### `isGALoaded()` → `boolean`
775
+
776
+ ---
777
+
778
+ ## 6. CookieConsent Component
779
+
780
+ **Files:** `src/components/CookieConsent/`
781
+
782
+ GDPR consent banner with a 3-category settings panel. Renders nothing if a previous decision is already stored in the `cookieConsent` cookie.
783
+
784
+ ```typescript
785
+ import { CookieConsent, getCookieConsentTexts } from 'webs-sdk';
786
+ import type {
787
+ CookieConsentProps,
788
+ CookieConsentTheme,
789
+ CookieConsentPreferences,
790
+ CookieConsentTexts,
791
+ } from 'webs-sdk';
792
+ ```
793
+
794
+ #### Basic usage
795
+
796
+ ```tsx
797
+ <CookieConsent
798
+ locale="en"
799
+ privacyPolicyUrl="https://myportal.com/privacy"
800
+ onAccept={() => updateGAConsent('granted')}
801
+ onReject={() => updateGAConsent('denied')}
802
+ onSaveSettings={(prefs) => updateGAConsent(prefs.analytical ? 'granted' : 'denied')}
803
+ />
804
+ ```
805
+
806
+ #### Props
807
+
808
+ | Prop | Type | Default | Description |
809
+ |---|---|---|---|
810
+ | `locale` | `string` | `'en'` | Language: `en`, `es`, `fr`, `nl`, `ar`, `de`, `fi`, `ro`, `tr` |
811
+ | `privacyPolicyUrl` | `string` | — | Privacy policy URL. Link is hidden if omitted |
812
+ | `config` | `CookieConsentConfig` | — | Cookie name, TTL, localStorage key overrides |
813
+ | `theme` | `CookieConsentTheme` | — | Visual customisation |
814
+ | `texts` | `Partial<CookieConsentTexts>` | — | Override specific translation strings |
815
+ | `onAccept` | `() => void` | — | Called when user clicks "Accept all" |
816
+ | `onReject` | `() => void` | — | Called when user clicks "Essential only" |
817
+ | `onSaveSettings` | `(prefs: CookieConsentPreferences) => void` | — | Called from the settings panel |
818
+
819
+ #### Theme
820
+
821
+ ```typescript
822
+ interface CookieConsentTheme {
823
+ backgroundColor?: string; // Supports CSS gradients
824
+ textColor?: string;
825
+ titleColor?: string;
826
+ accentColor?: string; // Shortcut: applies to Accept button, Reject border, links
827
+ acceptButtonBg?: string; // Supports CSS gradients (uses CSS 'background', not 'background-color')
828
+ acceptButtonText?: string;
829
+ essentialButtonText?: string;
830
+ essentialButtonBorder?: string;
831
+ linkColor?: string;
832
+ borderColor?: string;
833
+ borderRadius?: string;
834
+ fontFamily?: string;
835
+ zIndex?: number;
836
+ }
837
+ ```
838
+
839
+ The banner automatically generates a glow shadow from `accentColor` using 25% opacity (8-digit hex alpha).
840
+
841
+ #### Storage
842
+
843
+ | Key | Values | TTL |
844
+ |---|---|---|
845
+ | Cookie `cookieConsent` | `accepted` \| `rejected` \| `custom` | 1 year |
846
+ | `localStorage.TRACKING_PERMISSION` | `granted` \| `denied` | Permanent |
847
+ | `localStorage.FUNCTIONAL_PERMISSION` | `granted` \| `denied` | Permanent |
848
+
849
+ #### Consent flow
850
+
851
+ ```
852
+ Component mounts
853
+ ├── Cookie cookieConsent exists → component renders null (invisible)
854
+ └── No cookie → banner visible
855
+
856
+ Accept all → cookieConsent=accepted, TRACKING_PERMISSION=granted → onAccept()
857
+ Reject → cookieConsent=rejected, TRACKING_PERMISSION=denied → onReject()
858
+ Manage cookies → Settings panel:
859
+ - Mandatory (always on, not editable)
860
+ - Functional (checkbox → FUNCTIONAL_PERMISSION)
861
+ - Analytical (checkbox → TRACKING_PERMISSION)
862
+ → onSaveSettings({ functional, analytical })
863
+ ```
864
+
865
+ #### Getting translated texts manually
866
+
867
+ ```typescript
868
+ const texts = getCookieConsentTexts('es');
869
+ // Returns CookieConsentTexts with all strings in Spanish
870
+ ```
871
+
872
+ ---
873
+
874
+ ## 7. CDN Proxy
875
+
876
+ **File:** `src/libraries/cdn.ts`
877
+
878
+ Proxies asset requests to CloudFront. Implemented using Web API only (`fetch`, `Response`) — compatible with Next.js Edge Runtime.
879
+
880
+ ```typescript
881
+ import { handleCDNRequest } from 'webs-sdk';
882
+ import type { CDNOptions } from 'webs-sdk';
883
+
884
+ async function handleCDNRequest(
885
+ pathname: string, // e.g. '/cdn/assets/image.png'
886
+ options?: CDNOptions
887
+ ): Promise<Response | null> // null if pathname does not start with '/cdn/'
888
+
889
+ interface CDNOptions {
890
+ cdnBaseUrl?: string; // Default: 'https://dy822md8ge77v.cloudfront.net'
891
+ cacheSeconds?: number; // Default: 300 (5 minutes)
892
+ }
893
+ ```
894
+
895
+ **Response headers (on success):**
896
+
897
+ ```
898
+ Content-Type: <detected MIME type>
899
+ Cache-Control: public, max-age=300, s-maxage=300
900
+ Access-Control-Allow-Origin: *
901
+ Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, DELETE
902
+ ```
903
+
904
+ **Usage in HTML:**
905
+
906
+ ```html
907
+ <img src="/cdn/chatconnect/assets/flags/gbr.png" />
908
+ ```
909
+
910
+ > **Note:** In `next_template`, the CDN handler is **inlined** in `src/app/cdn/[...path]/route.ts` rather than imported from the SDK. This is intentional: the Edge Route Handler needs full control over headers and must not pull in Node.js dependencies. The SDK function is available for other contexts (non-Edge environments).
911
+
912
+ ---
913
+
914
+ ## 8. React Content Components
915
+
916
+ These components are self-contained widgets that call the Fluver AI API directly. They are designed to be dropped into any page in `next_template`.
917
+
918
+ ### Component pattern
919
+
920
+ Every content component exports three variants:
921
+
922
+ | Export | Use |
923
+ |---|---|
924
+ | `ComponentName` | Inline embeddable widget |
925
+ | `ComponentNamePage` | Full-page layout |
926
+ | `ComponentNameForYouCard` | Small card for "For You" section |
927
+
928
+ All components share the same props pattern:
929
+
930
+ ```typescript
931
+ interface ComponentProps {
932
+ config?: ComponentConfig; // API keys, endpoints, presets
933
+ theme?: ThemeConfig; // CSS class names or colour values
934
+ texts?: ComponentTexts; // i18n strings (all optional)
935
+ websiteId?: string; // For multi-portal tracking
936
+ onResult?: (data) => void; // Success callback
937
+ onError?: (err) => void; // Error callback
938
+ }
939
+ ```
940
+
941
+ ### Available components
942
+
943
+ ```typescript
944
+ import {
945
+ AvatarAI, AvatarAIPage, AvatarAIForYouCard,
946
+ CreativeFaceSwap, CreativeFaceSwapPage, CreativeFaceSwapForYouCard,
947
+ Wallpapers, WallpapersPage, WallpapersForYouCard,
948
+ WallpapersName, WallpapersNamePage, WallpapersNameForYouCard, AETemplatesSection,
949
+ Ringtone, RingtoneForYouCard,
950
+ MemeGenerator, MemeGeneratorForYouCard,
951
+ Quiz, QuizForYouCard,
952
+ Stickers, StickersForYouCard,
953
+ } from 'webs-sdk';
954
+ ```
955
+
956
+ ### ARFilters — DeepAR
957
+
958
+ Fullscreen AR camera experience powered by DeepAR. Requires a license key.
959
+
960
+ ```typescript
961
+ import { ARFilters, ARFiltersForYouCard, getARFiltersTexts } from 'webs-sdk';
962
+ import type { DeepARProps, DeepARFilter, DeepARTexts } from 'webs-sdk';
963
+
964
+ <ARFilters
965
+ licenseKey="your-deepar-license-key"
966
+ websiteId="myportal-web"
967
+ apiBaseUrl="https://api.fluver-ai.com"
968
+ downloadProxyUrl="/cdn" // CORS proxy for downloading filter assets
969
+ filters={[ // Explicit filter list (optional)
970
+ { file: 'beauty', thumbUrl: '/thumb.jpg' }
971
+ ]}
972
+ filterTag="ar-filters" // Alternatively, fetch filters from CMS by tag
973
+ locale="en"
974
+ texts={getARFiltersTexts('en')} // Required
975
+ onPhotoTaken={(url) => sharePhoto(url)}
976
+ onVideoRecorded={(url) => shareVideo(url)}
977
+ />
978
+ ```
979
+
980
+ ### SpinningWheel — Gamification
981
+
982
+ ```typescript
983
+ import { SpinningWheel, wheelThemes, SpinningWheelAPI, createSpinningWheelAPI } from 'webs-sdk';
984
+ import type { SpinningWheelProps, WheelSegment, SpinToWinConfig, WheelTheme, ThemeName } from 'webs-sdk';
985
+
986
+ <SpinningWheel
987
+ userId="12345"
988
+ websiteId="myportal-web"
989
+ theme="vodacom" // Available: 'vodacom' | 'totalgym' | 'womantoday' | 'afristream' | 'efc'
990
+ onResult={(segment) => handleWin(segment)}
991
+ canSpin={true}
992
+ alreadySpunToday={false}
993
+ />
994
+ ```
995
+
996
+ Pre-built theme configs: `vodacomConfig`, `totalGymConfig`, `womanTodayConfig`, `afristreamConfig`
997
+
998
+ ---
999
+
1000
+ ## 9. Esports Components
1001
+
1002
+ Three independent components for esports content portals.
1003
+
1004
+ ```typescript
1005
+ import {
1006
+ EsportsNews,
1007
+ EsportsVideos,
1008
+ EsportsLive,
1009
+ } from 'webs-sdk';
1010
+ import type {
1011
+ EsportsNewsProps, EsportsNewsConfig, EsportsNewsTheme,
1012
+ EsportsVideosProps, EsportsVideosConfig, EsportsVideosTheme,
1013
+ EsportsLiveProps, EsportsLiveConfig, EsportsLiveTheme,
1014
+ NewsItem, VideoItem, CalendarMatch,
1015
+ } from 'webs-sdk';
1016
+ ```
1017
+
1018
+ ### EsportsNews
1019
+
1020
+ ```tsx
1021
+ <EsportsNews
1022
+ config={{
1023
+ key: 'api-key',
1024
+ contents: [{
1025
+ preset: 'esports_news',
1026
+ apiProviderId: 'provider-id',
1027
+ category: [],
1028
+ tags: [],
1029
+ limit: 10,
1030
+ }]
1031
+ }}
1032
+ language="en"
1033
+ onNewsClick={(item: NewsItem) => router.push(`/news/${item.id}`)}
1034
+ theme={myNewsTheme}
1035
+ // Detail mode: pass newsId to render EsportsNewsDetail
1036
+ newsId="article-123"
1037
+ newsData={item} // optional — skip fetch if you already have the data
1038
+ onBack={() => router.back()}
1039
+ />
1040
+ ```
1041
+
1042
+ ### EsportsVideos
1043
+
1044
+ ```tsx
1045
+ <EsportsVideos
1046
+ config={{ key: 'api-key', contents: [...] }}
1047
+ language="en"
1048
+ onVideoClick={(item: VideoItem) => openPlayer(item.video_url)}
1049
+ theme={myVideosTheme}
1050
+ />
1051
+ ```
1052
+
1053
+ ### EsportsLive
1054
+
1055
+ Displays live esports matches with teams, scores, and stream links.
1056
+
1057
+ ```tsx
1058
+ <EsportsLive
1059
+ language="en"
1060
+ timezone="Europe/London"
1061
+ onLiveClick={(item) => openStream(item.streamUrl)}
1062
+ onGameClick={(gameId) => filterByGame(gameId)}
1063
+ onMatchClick={(match: CalendarMatch) => openMatch(match)}
1064
+ theme={myLiveTheme}
1065
+ // Match detail mode:
1066
+ matchId="serie-123"
1067
+ matchData={calendarMatch} // optional — skip fetch if data is available
1068
+ onBack={() => setMatchId(null)}
1069
+ website_id="myportal-web"
1070
+ />
1071
+ ```
1072
+
1073
+ **Key types:**
1074
+
1075
+ ```typescript
1076
+ interface NewsItem {
1077
+ id: string;
1078
+ title: string;
1079
+ description: string;
1080
+ excerpt: string;
1081
+ category: string;
1082
+ tag: string;
1083
+ thumbnail: string;
1084
+ images: string[];
1085
+ date: string;
1086
+ time: string;
1087
+ }
1088
+
1089
+ interface VideoItem extends NewsItem {
1090
+ video_url: string;
1091
+ duration: string;
1092
+ }
1093
+
1094
+ interface CalendarMatch {
1095
+ serie_id: string;
1096
+ serie_name: string;
1097
+ serie_status: string;
1098
+ serie_live: boolean;
1099
+ team_one: Team;
1100
+ team_two: Team;
1101
+ game_name: string;
1102
+ tournament_name: string;
1103
+ stage_name: string;
1104
+ }
1105
+
1106
+ interface Team {
1107
+ id: string;
1108
+ name: string;
1109
+ image: string;
1110
+ seed: number;
1111
+ score: number;
1112
+ winner: boolean;
1113
+ }
1114
+ ```
1115
+
1116
+ ---
1117
+
1118
+ ## 10. UI Component Library
1119
+
1120
+ A set of accessible UI primitives based on Radix UI and Tailwind CSS.
1121
+
1122
+ ```typescript
1123
+ import {
1124
+ Button,
1125
+ Input,
1126
+ Dialog,
1127
+ Accordion,
1128
+ Badge,
1129
+ Carousel,
1130
+ Checkbox,
1131
+ DropdownMenu,
1132
+ Label,
1133
+ Select,
1134
+ Separator,
1135
+ Skeleton,
1136
+ Tabs,
1137
+ Tooltip,
1138
+ // ...and more
1139
+ } from 'webs-sdk/ui';
1140
+ ```
1141
+
1142
+ > Import from `'webs-sdk/ui'`, not from `'webs-sdk'`.
1143
+
1144
+ These components follow the Radix UI composition pattern. Refer to [Radix UI documentation](https://www.radix-ui.com/) for usage — the API surface is the same.
1145
+
1146
+ ---
1147
+
1148
+ ## 11. Video Validation Utilities
1149
+
1150
+ Validates whether a streaming URL (HLS, DASH, MP4) is actually reachable before attempting playback.
1151
+
1152
+ ```typescript
1153
+ import {
1154
+ validateVideoUrl,
1155
+ validateVideoBatch,
1156
+ getCacheStats,
1157
+ clearValidationCache,
1158
+ cleanExpiredCache,
1159
+ } from 'webs-sdk';
1160
+ import {
1161
+ useValidateVideoOnMount,
1162
+ useLazyVideoValidation,
1163
+ } from 'webs-sdk';
1164
+ import type { VideoValidationState, VideoValidationConfig } from 'webs-sdk';
1165
+ ```
1166
+
1167
+ ### Pure API (no React)
1168
+
1169
+ ```typescript
1170
+ type VideoValidationState = 'valid' | 'invalid' | 'unknown' | 'validating';
1171
+
1172
+ interface VideoValidationConfig {
1173
+ cacheTTL?: number; // Default: 300000 ms (5 min)
1174
+ headRequestTimeout?: number; // Default: 8000 ms
1175
+ videoElementTimeout?: number; // Default: 10000 ms
1176
+ verbose?: boolean; // Default: false
1177
+ }
1178
+
1179
+ // Validate a single URL
1180
+ const state: VideoValidationState = await validateVideoUrl(url, config?);
1181
+
1182
+ // Validate multiple URLs with controlled concurrency
1183
+ const results: Map<string, VideoValidationState> = await validateVideoBatch(urls, 3);
1184
+ ```
1185
+
1186
+ **Validation strategy:**
1187
+ ```
1188
+ URL → check cache (TTL: 5 min)
1189
+ └── not cached → HEAD request (8 s timeout)
1190
+ ├── 200-299 → 'valid'
1191
+ ├── network error → fallback: video element test (10 s)
1192
+ │ ├── loadedmetadata → 'valid'
1193
+ │ └── error event → 'invalid'
1194
+ └── 4xx / 5xx → 'invalid'
1195
+ ```
1196
+
1197
+ ### React hooks
1198
+
1199
+ ```typescript
1200
+ // Auto-validate on mount — useful to skip rendering broken streams
1201
+ const { isValid, isInvalid, isValidating, validationState } =
1202
+ useValidateVideoOnMount(videoUrl, config?);
1203
+
1204
+ // Manual / on-demand validation
1205
+ const { validate, isValid, isInvalid, isValidating } =
1206
+ useLazyVideoValidation(config?);
1207
+
1208
+ await validate(videoUrl);
1209
+ ```
1210
+
1211
+ ---
1212
+
1213
+ ## 12. TypeScript Types Reference
1214
+
1215
+ ```typescript
1216
+ // Authentication
1217
+ interface AuthParams {
1218
+ login: string;
1219
+ password: string;
1220
+ website_id: string;
1221
+ }
1222
+
1223
+ interface AuthResult {
1224
+ status: 'success' | 'error';
1225
+ data?: any;
1226
+ error?: string;
1227
+ errorKey?: string;
1228
+ }
1229
+
1230
+ interface UnsubscribeParams {
1231
+ user_id: string;
1232
+ callback_url?: string;
1233
+ org?: string;
1234
+ }
1235
+
1236
+ // Auto-login
1237
+ interface AutoLoginResult {
1238
+ status: string;
1239
+ redirectUrl?: string;
1240
+ cookies?: CookieToSet[];
1241
+ error?: string;
1242
+ errorKey?: string;
1243
+ }
1244
+
1245
+ interface CookieToSet {
1246
+ name: string;
1247
+ value: string;
1248
+ options: {
1249
+ path: string;
1250
+ maxAge: number;
1251
+ sameSite: 'lax' | 'strict' | 'none';
1252
+ secure: boolean;
1253
+ httpOnly: boolean;
1254
+ expires?: Date;
1255
+ };
1256
+ }
1257
+
1258
+ // Analytics
1259
+ interface GAOptions {
1260
+ configParams?: Record<string, unknown>;
1261
+ sendPageView?: boolean;
1262
+ debug?: boolean;
1263
+ }
1264
+
1265
+ // CDN
1266
+ interface CDNOptions {
1267
+ cdnBaseUrl?: string;
1268
+ cacheSeconds?: number;
1269
+ }
1270
+
1271
+ // Cookie Consent
1272
+ interface CookieConsentPreferences {
1273
+ functional: boolean;
1274
+ analytical: boolean;
1275
+ }
1276
+ ```
1277
+
1278
+ ---
1279
+
1280
+ ## 13. Production vs Pre-production
1281
+
1282
+ | Namespace | Backend URL | Use |
1283
+ |---|---|---|
1284
+ | `WebsSDK.*` | `https://bc1742.gways.org` | Production data |
1285
+ | `WebsSDK.pre.*` | `https://bc1742-pre.gways.org` | Testing, development |
1286
+
1287
+ `WebsSDK.pre` is **lazy-loaded** — the pre-production instances are created only on first access, so importing the SDK does not spin up pre-production connections on production servers.
1288
+
1289
+ In `next_template`, **all API routes use `WebsSDK.pre.*`** by default. This is intentional: the template is configured for the pre-production environment during development and the CI pipeline handles environment switching on deployment.
1290
+
1291
+ ```typescript
1292
+ // In any next_template API route:
1293
+ const WebsSDK = (await import('webs-sdk')).default;
1294
+ const result = await WebsSDK.pre.AuthManager.authUser({ login, password, website_id });
1295
+ // ^^^^ pre-production
1296
+ ```
1297
+
1298
+ ---
1299
+
1300
+ ## 14. Error Patterns & Security
1301
+
1302
+ ### Consistent error shape
1303
+
1304
+ All SDK methods return the same structure:
1305
+
1306
+ ```typescript
1307
+ {
1308
+ status: 'success' | 'error',
1309
+ data?: any, // Present on success
1310
+ error?: string, // Human-readable message
1311
+ errorKey?: string // i18n key for frontend display
1312
+ }
1313
+ ```
1314
+
1315
+ ### Known error keys
1316
+
1317
+ | Key | Source | Meaning |
1318
+ |---|---|---|
1319
+ | `invalidData` | All methods | Missing required parameters |
1320
+ | `userNotFound` | AuthManager | No account with that login |
1321
+ | `invalidCredentials` | AuthManager | Wrong password |
1322
+ | `accountDisabled` | AuthManager | Account administratively disabled |
1323
+ | `userNotActive` | AuthManager | Email not verified or pending activation |
1324
+ | `authFailed` | AuthManager | Generic auth failure |
1325
+ | `userAlreadyExists` | createUser | Duplicate email/username |
1326
+ | `signupFailed` | createUser | Registration rejected by backend |
1327
+ | `unsubscribeFailed` | unsubscribeUser | Cancellation failed |
1328
+ | `114` | unsubscribeUser | No active subscriptions to cancel |
1329
+ | `changePasswordFailed` | changePassword | Password update rejected |
1330
+ | `invalidFormat` | uploadProfilePhoto | File type not accepted |
1331
+ | `fileTooLarge` | uploadProfilePhoto | File exceeds 5 MB limit |
1332
+ | `serverError` | All methods | Backend unreachable or 5xx response |
1333
+
1334
+ ### Security practices
1335
+
1336
+ - **Passwords** are never logged — log statements mask with `'***'`
1337
+ - **HttpOnly cookies** — `userSession` is set server-side with `httpOnly: true, secure: true, sameSite: 'lax'`
1338
+ - **File uploads** — type and size validated before sending to backend
1339
+ - **Session validation** — when `navigation_session: true` in product config, every request validates the token against the backend
1340
+ - **Pre-production** — credentials are never sent to production endpoints from the template