webs-sdk 0.19.62 → 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.
- package/AGENTS.md +44 -0
- package/CLAUDE.md +80 -0
- package/README.md +63 -1
- package/dist/components/SpinningWheel/index.d.ts +1 -1
- package/dist/components/SpinningWheel/index.d.ts.map +1 -1
- package/dist/components/SpinningWheel/index.js +2 -1
- package/dist/components/SpinningWheel/index.js.map +1 -1
- package/dist/components/ui/ConfiguredMediaPlayer.d.ts +49 -0
- package/dist/components/ui/ConfiguredMediaPlayer.d.ts.map +1 -0
- package/dist/components/ui/ConfiguredMediaPlayer.js +111 -0
- package/dist/components/ui/ConfiguredMediaPlayer.js.map +1 -0
- package/dist/components/ui/badge.d.ts +1 -0
- package/dist/components/ui/badge.d.ts.map +1 -1
- package/dist/components/ui/badge.js +1 -1
- package/dist/components/ui/badge.js.map +1 -1
- package/dist/components/ui/compose-refs.d.ts +6 -0
- package/dist/components/ui/compose-refs.d.ts.map +1 -0
- package/dist/components/ui/compose-refs.js +57 -0
- package/dist/components/ui/compose-refs.js.map +1 -0
- package/dist/components/ui/dropdown-menu.d.ts +3 -1
- package/dist/components/ui/dropdown-menu.d.ts.map +1 -1
- package/dist/components/ui/dropdown-menu.js +2 -2
- package/dist/components/ui/dropdown-menu.js.map +1 -1
- package/dist/components/ui/index.d.ts +2 -0
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +2 -0
- package/dist/components/ui/index.js.map +1 -1
- package/dist/components/ui/media-player.d.ts +127 -0
- package/dist/components/ui/media-player.d.ts.map +1 -0
- package/dist/components/ui/media-player.js +1633 -0
- package/dist/components/ui/media-player.js.map +1 -0
- package/dist/components/ui/tooltip.d.ts +3 -1
- package/dist/components/ui/tooltip.d.ts.map +1 -1
- package/dist/components/ui/tooltip.js +2 -2
- package/dist/components/ui/tooltip.js.map +1 -1
- package/dist/components/ui/use-lazy-ref.d.ts +4 -0
- package/dist/components/ui/use-lazy-ref.d.ts.map +1 -0
- package/dist/components/ui/use-lazy-ref.js +45 -0
- package/dist/components/ui/use-lazy-ref.js.map +1 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -0
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/libraries/auth.d.ts.map +1 -1
- package/dist/libraries/auth.js +2 -2
- package/dist/libraries/auth.js.map +1 -1
- package/dist/libraries/clarity.d.ts +23 -0
- package/dist/libraries/clarity.d.ts.map +1 -0
- package/dist/libraries/clarity.js +110 -0
- package/dist/libraries/clarity.js.map +1 -0
- package/dist/libraries/index.d.ts +1 -0
- package/dist/libraries/index.d.ts.map +1 -1
- package/dist/libraries/index.js +3 -1
- package/dist/libraries/index.js.map +1 -1
- package/dist/libraries/networking.d.ts +4 -0
- package/dist/libraries/networking.d.ts.map +1 -1
- package/dist/libraries/networking.js +42 -6
- package/dist/libraries/networking.js.map +1 -1
- package/dist/libraries/session.d.ts.map +1 -1
- package/dist/libraries/session.js +2 -0
- package/dist/libraries/session.js.map +1 -1
- package/dist/network.d.ts +12 -0
- package/dist/network.d.ts.map +1 -0
- package/dist/network.js +15 -0
- package/dist/network.js.map +1 -0
- package/dist/server.d.ts +54 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +74 -0
- package/dist/server.js.map +1 -0
- package/dist/types/media-chrome-store-types.d.ts +107 -0
- package/dist/types/media-chrome-store-types.d.ts.map +1 -0
- package/dist/types/media-chrome-store-types.js +3 -0
- package/dist/types/media-chrome-store-types.js.map +1 -0
- package/dist/utils/QRCode.d.ts +1 -0
- package/dist/utils/QRCode.d.ts.map +1 -0
- package/dist/utils/QRCode.js +2 -0
- package/dist/utils/QRCode.js.map +1 -0
- package/dist/utils/imageValidation.d.ts +6 -0
- package/dist/utils/imageValidation.d.ts.map +1 -0
- package/dist/utils/imageValidation.js +64 -0
- package/dist/utils/imageValidation.js.map +1 -0
- package/docs/CONFIGURED_MEDIA_PLAYER.md +40 -0
- package/docs/CORP_AUTH_USAGE.md +687 -0
- package/docs/EXAMPLES_WALLPAPERSNAME_IMPLEMENTATION.md +1445 -0
- package/docs/EXAMPLE_ENDPOINTS_COMUNICATION.md +55 -0
- package/docs/EXTERNAL_SDK_GUIDE.md +1340 -0
- package/docs/FOR_YOU_360_FACESWAP_FIX.md +400 -0
- package/docs/FOR_YOU_IMPLEMENTATION_GUIDE.md +1413 -0
- package/docs/FOR_YOU_QUIZ_ARCHITECTURE.md +753 -0
- package/docs/FOR_YOU_SDK_WALLPAPERSNAME_BUG.md +106 -0
- package/docs/FOR_YOU_WALLPAPERSNAME_ENHANCEMENT_PLAN.md +210 -0
- package/docs/LEGAL_USAGE.md +190 -0
- package/docs/STREAK_IMPLEMENTATION_GUIDE.md +430 -0
- package/package.json +29 -6
- package/scripts/install-agent-skill.cjs +34 -0
- package/skills/webs-sdk-package/SKILL.md +11 -0
- package/skills/webs-sdk-package/references/components.md +30 -0
- package/skills/webs-sdk-package/references/integration.md +32 -0
- 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
|
@@ -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
|