@returningai/widget-sdk 1.0.3 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,17 +1,17 @@
1
1
  # ReturningAI Widget SDK
2
2
 
3
- A Shadow DOM-isolated Web Component SDK for embedding ReturningAI widgets on any customer website. Drop-in replacement for the legacy `widget-loader.js` — identical embed attributes, zero CSS conflicts.
3
+ A Web Component SDK for embedding ReturningAI widgets on any customer website. Supports both **iframe mode** (Shadow DOM isolation) and **bundle mode** (direct DOM rendering). Drop-in replacement for the legacy `widget-loader.js`.
4
4
 
5
5
  ---
6
6
 
7
7
  ## Table of Contents
8
8
 
9
9
  - [Architecture](#architecture)
10
- - [Shadow DOM Implementation](#shadow-dom-implementation)
11
- - [Dependencies](#dependencies)
10
+ - [Embed Modes](#embed-modes)
12
11
  - [Usage](#usage)
13
12
  - [Script Tag (Legacy Embed)](#script-tag-legacy-embed)
14
13
  - [Web Component](#web-component)
14
+ - [Bundle Mode (Non-Iframe)](#bundle-mode-non-iframe)
15
15
  - [Configuration Attributes](#configuration-attributes)
16
16
  - [Public API](#public-api)
17
17
  - [Build Process](#build-process)
@@ -21,29 +21,46 @@ A Shadow DOM-isolated Web Component SDK for embedding ReturningAI widgets on any
21
21
 
22
22
  ## Architecture
23
23
 
24
+ The SDK renders widgets in one of two modes, chosen automatically by the presence of the `bundle-url` attribute:
25
+
26
+ ### Iframe Mode (default)
27
+
28
+ ```
29
+ Customer page DOM
30
+ └── <rai-store-widget> ← custom element (or any type-specific tag)
31
+ └── Shadow Root [closed]
32
+ ├── <style> ← all CSS scoped here, never leaks
33
+ ├── <div class="rai-loader"> ← animated loader (hidden after ready)
34
+ ├── <div class="rai-error"> ← error state (hidden until error)
35
+ └── <iframe src="widget-url"> ← actual widget content
36
+ ```
37
+
38
+ ### Bundle Mode (when `bundle-url` is set)
39
+
24
40
  ```
25
41
  Customer page DOM
26
- └── <div id="returning-ai-widget-{id}"> customer's container (unchanged)
27
- └── <rai-widget> ← custom element (auto-mounted)
28
- └── Shadow Root [closed]
29
- ├── <style> all CSS scoped here, never leaks
30
- ├── <div class="rai-loader"> animated loader (hidden after ready)
31
- ├── <div class="rai-error"> ← error state (hidden until error)
32
- └── <iframe src="widget-url"> ← actual widget content
42
+ └── <rai-store-widget> custom element (or any type-specific tag)
43
+ ├── Shadow Root [closed]
44
+ │ ├── <style> ← SDK loader/error CSS
45
+ │ └── <slot> renders light-DOM children through shadow
46
+ └── <div> (light DOM) widget mounts here, CSS cascade works normally
47
+ └── [React app from IIFE bundle]
33
48
  ```
34
49
 
50
+ Bundle mode loads the widget's JavaScript bundle directly into the page (no iframe). CSS cascades from `html[data-theme]` into the widget naturally, making theme integration seamless.
51
+
35
52
  ### Module Responsibilities
36
53
 
37
54
  | Module | File | Purpose |
38
55
  |--------|------|---------|
39
- | Base Web Component | `src/BaseWidget.ts` | Abstract `HTMLElement` subclass; owns Shadow Root, auth lifecycle, iframe creation, lazy loading, DOM events, public API |
56
+ | Base Web Component | `src/BaseWidget.ts` | Abstract `HTMLElement` subclass; owns Shadow Root, auth lifecycle, iframe/bundle rendering, lazy loading, DOM events, public API |
40
57
  | Store Widget | `src/StoreWidget.ts` | Extends `BaseWidget`; implements `buildWidgetUrl()` for the store micro-frontend |
41
- | Channel Widget | `src/ChannelWidget.ts` | Extends `BaseWidget`; appends `widgetId` to base URL or passes full URL through |
58
+ | Channel Widget | `src/ChannelWidget.ts` | Extends `BaseWidget`; appends encoded channel ID to URL |
42
59
  | Milestone Widget | `src/MilestoneWidget.ts` | Extends `BaseWidget`; same URL strategy as `ChannelWidget` |
43
60
  | Social Widget | `src/SocialWidget.ts` | Extends `BaseWidget`; same URL strategy as `ChannelWidget` |
44
61
  | Currency Widget | `src/CurrencyWidget.ts` | Extends `BaseWidget`; same URL strategy as `ChannelWidget` |
45
- | Auth | `src/core/auth.ts` | Serverless auth with exponential backoff retry, proxy auth (authenticated embed), token refresh (concurrency lock), logout, error settings fetch |
46
- | Storage | `src/core/storage.ts` | `localStorage` helpers scoped to `{prefix}-{widgetId}-*`; access token never written to disk |
62
+ | Auth | `src/core/auth.ts` | Serverless auth with exponential backoff retry, proxy auth (authenticated embed), embed token validation (access key embed), token refresh (concurrency lock), logout, error settings fetch |
63
+ | Storage | `src/core/storage.ts` | `localStorage` helpers scoped to `{prefix}-{communityId}-*`; access token never written to disk |
47
64
  | postMessage | `src/core/postmessage.ts` | Sends token (+ optional `customData`) to iframe; debounced height updates; emits DOM events; handles `WIDGET_READY`, `WIDGET_HEIGHT_UPDATE`, `WIDGET_LOGOUT`, `RETURNINGAI_WIDGET_REQUEST_TOKEN` |
48
65
  | Styles | `src/styles/widget.css` | Loader animation, error state, retry button, iframe fade-in — all scoped inside Shadow DOM |
49
66
  | Entry | `src/index.ts` | Registers all 5 custom elements; bootstraps from `<script>` tag for legacy embeds; routes by `widget-type`; exposes `window.ReturningAIWidget` |
@@ -53,21 +70,43 @@ Customer page DOM
53
70
  ```
54
71
  Page load
55
72
 
56
- ├─ auth-url present? (authenticated embed)
57
- │ Yes ──► authenticateViaProxy() ──► createIframe()
73
+ ├─ embed-token present? (Access Key Embed)
74
+ │ Yes ──► validateEmbedToken()
75
+ │ ├─ valid ──► continue below
76
+ │ └─ invalid ──► showError() — stop
77
+ │ No ──► skip validation, continue below
78
+
79
+ ├─ auth-url present? (Authenticated Embed)
80
+ │ Yes ──► authenticateViaProxy() ──► launch widget
58
81
  │ No ──► loadFromStorage()
59
- │ ├─ refresh token found ──► refreshAccessToken() ──► createIframe()
60
- │ └─ no token ──► authenticateServerless() ──► createIframe()
82
+ │ ├─ refresh token found ──► refreshAccessToken() ──► launch widget
83
+ │ └─ no token ──► authenticateServerless() ──► launch widget
61
84
 
62
- └─ iframe.onload
63
- └─ sendTokenToWidget() postMessage with access token
64
- schedulePeriodicSync() resend every 2 min
65
-
66
- Token nearing expiry (1 min early)
67
- ├─ authenticated embed ──► authenticateViaProxy() ──► sendTokenToWidget()
68
- └─ public embed ──► refreshAccessToken()
69
- ├─ 401/403 ──► re-authenticate serverless (token family rotated)
70
- └─ success ──► sendTokenToWidget()
85
+ └─ launch widget
86
+ ├─ bundle-url present? ──► mountWidget() (load IIFE, render in light DOM)
87
+ └─ no bundle-url ──► createIframe() (standard iframe mode)
88
+ ```
89
+
90
+ **Access Key Embed flow** (credential exchange happens entirely server-side):
91
+
92
+ ```
93
+ Customer server
94
+ │ POST /v2/api/widget-access-keys/token
95
+ │ Body: { accessId, accessKey } ← stored in env vars, never in HTML
96
+
97
+ RAI backend — validates credentials, signs 15-min JWT
98
+ │ Response: { embedToken, expiresIn: 900 }
99
+
100
+ Customer server injects embedToken into HTML
101
+ │ <rai-store-widget embed-token="eyJ...">
102
+
103
+ Browser loads page — SDK reads embed-token attribute
104
+ │ POST /v2/api/widget-access-keys/validate
105
+ │ Body: { embedToken }
106
+
107
+ RAI backend — verifies JWT signature + checks key not revoked
108
+ ├─ valid ──► widget initialises normally
109
+ └─ invalid ──► showError()
71
110
  ```
72
111
 
73
112
  **Token storage strategy**
@@ -77,9 +116,9 @@ Token nearing expiry (1 min early)
77
116
  | Access token | Memory only (`WidgetState.accessToken`) | ~5 min |
78
117
  | Refresh token | `localStorage` | 7 days |
79
118
 
80
- The access token is never written to `localStorage`. On every page load the refresh token is exchanged for a fresh access token before the iframe mounts.
119
+ The access token is never written to `localStorage`. On every page load the refresh token is exchanged for a fresh access token before the widget mounts.
81
120
 
82
- ### postMessage Protocol
121
+ ### postMessage Protocol (iframe mode only)
83
122
 
84
123
  Messages the SDK sends **to** the widget iframe:
85
124
 
@@ -101,29 +140,31 @@ All messages are origin-validated against `config.widgetDomain`.
101
140
 
102
141
  ---
103
142
 
143
+ ## Embed Modes
144
+
145
+ The SDK supports three embed modes depending on your security requirements.
146
+
147
+ | | Public Embed | Access Key Embed | Authenticated Embed |
148
+ |---|---|---|---|
149
+ | Credentials in HTML | None | Short-lived token only | None |
150
+ | Backend required | **No** | Yes — one call per page load | Yes — one endpoint |
151
+ | Who generates auth | SDK calls RAI directly | Your server calls RAI, then token in HTML | Your server calls RAI with API key |
152
+ | `embed-token` attribute | Absent | **Required** | Absent |
153
+ | `auth-url` attribute | Absent | Absent | **Required** |
154
+
155
+ ---
156
+
104
157
  ## Shadow DOM Implementation
105
158
 
106
- The SDK uses a **closed** Shadow Root (`mode: 'closed'`), which means:
159
+ In iframe mode, the SDK uses a **closed** Shadow Root (`mode: 'closed'`), which means:
107
160
 
108
161
  - Customer page CSS cannot reach any element inside the widget
109
162
  - The widget's loader and error styles are fully encapsulated
110
163
  - No class name collisions with customer frameworks (Tailwind, Bootstrap, etc.)
111
164
 
112
- ### How it is attached
113
-
114
- ```typescript
115
- // StoreWidget.ts — constructor
116
- this.shadow = this.attachShadow({ mode: 'closed' })
117
-
118
- // connectedCallback — CSS injected as a <style> tag inside the shadow root
119
- const style = document.createElement('style')
120
- style.textContent = widgetCSS // inlined at build time via Vite ?inline import
121
- this.shadow.appendChild(style)
122
- ```
123
-
124
- CSS custom properties (`--rai-accent`, `--rai-loader-bg`, `--rai-text4`) are set on the host element and cascade into the Shadow Root, allowing theme-aware colours without breaking encapsulation.
165
+ In bundle mode, widget content renders in the **light DOM** (through a `<slot>`) so that `html[data-theme]` and Tailwind CSS cascade into the widget naturally.
125
166
 
126
- ### Theme variables
167
+ ### Theme variables (iframe mode)
127
168
 
128
169
  | Variable | Light | Dark |
129
170
  |----------|-------|------|
@@ -144,7 +185,7 @@ None. The SDK ships as a single self-contained IIFE with no external runtime dep
144
185
 
145
186
  | Package | Version | Purpose |
146
187
  |---------|---------|---------|
147
- | `vite` | ^5.1 | Bundles TypeScript + inlines CSS single IIFE |
188
+ | `vite` | ^5.1 | Bundles TypeScript + inlines CSS into a single IIFE |
148
189
  | `typescript` | ^5.3 | Type checking and compilation |
149
190
  | `terser` | ^5.46 | Minification for production builds |
150
191
 
@@ -169,16 +210,16 @@ Zero changes required to existing embed HTML. Point `src` at the SDK and keep al
169
210
  ```html
170
211
  <!-- 1. Container div (unchanged from current embed) -->
171
212
  <div
172
- id="returning-ai-widget-YOUR_WIDGET_ID"
213
+ id="returning-ai-widget-YOUR_COMMUNITY_ID"
173
214
  style="width: 100%; height: 600px;"
174
215
  ></div>
175
216
 
176
217
  <!-- 2. SDK script tag -->
177
218
  <script
178
219
  src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
179
- data-widget-id="YOUR_WIDGET_ID"
220
+ data-community-id="YOUR_COMMUNITY_ID"
180
221
  data-widget-type="store"
181
- data-container="returning-ai-widget-YOUR_WIDGET_ID"
222
+ data-container="returning-ai-widget-YOUR_COMMUNITY_ID"
182
223
  data-theme="dark"
183
224
  data-width="100%"
184
225
  data-height="600px"
@@ -189,7 +230,53 @@ Zero changes required to existing embed HTML. Point `src` at the SDK and keep al
189
230
  ></script>
190
231
  ```
191
232
 
192
- The SDK scans for the loader `<script>` tag, reads its `data-*` attributes, creates a `<rai-widget>` element, and mounts it inside the container div.
233
+ The SDK scans for the loader `<script>` tag, reads its `data-*` attributes, creates the appropriate widget element, and mounts it inside the container div.
234
+
235
+ ### Access Key Embed
236
+
237
+ Credentials live only in your server environment. Your server calls RAI to get a short-lived embed token and injects it into the page response — no secret ever reaches the browser.
238
+
239
+ ```
240
+ Your server env:
241
+ RAI_ACCESS_ID = rai_...
242
+ RAI_ACCESS_KEY = pk_... ← never in HTML
243
+
244
+ Per-request (server-side):
245
+ POST /v2/api/widget-access-keys/token
246
+ → { embedToken, expiresIn: 900 }
247
+
248
+ Page HTML:
249
+ <rai-store-widget embed-token="eyJ..." ...>
250
+ ```
251
+
252
+ Node.js / Express example:
253
+
254
+ ```js
255
+ app.get('/page', async (req, res) => {
256
+ const { data } = await fetch(`${process.env.RAI_API_URL}/v2/api/widget-access-keys/token`, {
257
+ method: 'POST',
258
+ headers: { 'Content-Type': 'application/json' },
259
+ body: JSON.stringify({
260
+ accessId: process.env.RAI_ACCESS_ID,
261
+ accessKey: process.env.RAI_ACCESS_KEY,
262
+ }),
263
+ }).then(r => r.json())
264
+
265
+ res.render('page', { embedToken: data.embedToken })
266
+ })
267
+ ```
268
+
269
+ ```html
270
+ <!-- page.html -->
271
+ <rai-store-widget
272
+ community-id="YOUR_COMMUNITY_ID"
273
+ api-url="YOUR_API_URL"
274
+ embed-token="<%= embedToken %>"
275
+ data-email="user@example.com"
276
+ ></rai-store-widget>
277
+ ```
278
+
279
+ The SDK validates the token on load. If expired (> 15 min since page render), the widget shows the error screen — users should refresh the page.
193
280
 
194
281
  ### Web Component
195
282
 
@@ -197,31 +284,31 @@ For customers using a JavaScript framework (React, Vue, Angular), import the SDK
197
284
 
198
285
  | Tag | Widget type |
199
286
  |-----|-------------|
200
- | `<rai-widget>` | `store` |
287
+ | `<rai-store-widget>` | `store` |
201
288
  | `<rai-channel-widget>` | `channel` |
202
289
  | `<rai-milestone-widget>` | `milestone` |
203
290
  | `<rai-social-widget>` | `social` |
204
291
  | `<rai-currency-widget>` | `currency-view` |
292
+ | `<rai-widget>` | `store` *(deprecated alias — use `<rai-store-widget>`)* |
205
293
 
206
294
  ```html
207
295
  <script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"></script>
208
296
 
209
297
  <!-- Store widget -->
210
- <rai-widget
211
- widget-id="YOUR_WIDGET_ID"
212
- widget-type="store"
298
+ <rai-store-widget
299
+ community-id="YOUR_COMMUNITY_ID"
213
300
  theme="dark"
214
301
  width="100%"
215
302
  height="600px"
216
303
  api-url="YOUR_API_URL"
217
304
  widget-url="YOUR_WIDGET_URL"
218
305
  data-email="user@example.com"
219
- ></rai-widget>
306
+ ></rai-store-widget>
220
307
 
221
- <!-- Channel widget -->
308
+ <!-- Channel widget (requires channel-id) -->
222
309
  <rai-channel-widget
223
- widget-id="YOUR_WIDGET_ID"
224
- widget-type="channel"
310
+ community-id="YOUR_COMMUNITY_ID"
311
+ channel-id="YOUR_CHANNEL_ID"
225
312
  theme="dark"
226
313
  api-url="YOUR_API_URL"
227
314
  widget-url="YOUR_WIDGET_URL"
@@ -237,9 +324,8 @@ import '@returningai/widget-sdk'
237
324
 
238
325
  export function WidgetEmbed() {
239
326
  return (
240
- <rai-widget
241
- widget-id="YOUR_WIDGET_ID"
242
- widget-type="store"
327
+ <rai-store-widget
328
+ community-id="YOUR_COMMUNITY_ID"
243
329
  theme="dark"
244
330
  height="600px"
245
331
  data-email={currentUser.email}
@@ -248,20 +334,54 @@ export function WidgetEmbed() {
248
334
  }
249
335
  ```
250
336
 
337
+ ### Bundle Mode (Non-Iframe)
338
+
339
+ When you set the `bundle-url` attribute, the widget renders directly in the page DOM instead of inside an iframe. This gives full CSS cascade — `html[data-theme]` and Tailwind classes apply to the widget content naturally.
340
+
341
+ ```html
342
+ <rai-store-widget
343
+ community-id="YOUR_COMMUNITY_ID"
344
+ api-url="YOUR_API_URL"
345
+ bundle-url="/path/to/store-widget.js"
346
+ embed-token="eyJ..."
347
+ data-email="user@example.com"
348
+ theme="dark"
349
+ width="100%"
350
+ height="100vh"
351
+ eager
352
+ ></rai-store-widget>
353
+ ```
354
+
355
+ Each widget type has its own IIFE bundle and global name:
356
+
357
+ | Widget type | Bundle global |
358
+ |-------------|--------------|
359
+ | `store` | `RaiStoreWidget` |
360
+ | `channel` | `RaiChannelWidget` |
361
+ | `social` | `RaiSocialWidget` |
362
+ | `milestone` | `RaiMilestoneWidget` |
363
+ | `currency-view` | `RaiCurrencyWidget` |
364
+
365
+ The bundle must export a `mount(container, config)` function on its global (e.g. `window.RaiStoreWidget.mount`).
366
+
251
367
  ### Configuration Attributes
252
368
 
253
369
  All attributes can be provided with or without the `data-` prefix.
254
370
 
255
371
  | Attribute | Required | Default | Description |
256
372
  |-----------|----------|---------|-------------|
257
- | `widget-id` | Yes | — | MongoDB ObjectId of the widget |
373
+ | `community-id` | **Yes** | — | Your community ID from Community Settings |
374
+ | `channel-id` | Channel only | — | Channel ID — required when `widget-type` is `channel` |
258
375
  | `widget-type` | No | `store` | `store`, `channel`, `milestone`, `social`, `currency-view` |
259
376
  | `theme` | No | `light` | `light` or `dark` |
260
377
  | `container` | No | `returning-ai-widget-{id}` | ID of the container element |
261
- | `width` | No | `100%` | CSS width of the iframe |
262
- | `height` | No | `600px` | Initial CSS height (auto-resized by `WIDGET_HEIGHT_UPDATE`) |
378
+ | `width` | No | `100%` | CSS width |
379
+ | `height` | No | `600px` | Initial CSS height (auto-resized by `WIDGET_HEIGHT_UPDATE` in iframe mode) |
263
380
  | `api-url` | No | — | Auth API base URL — from Community Settings |
264
- | `widget-url` | No | — | URL served inside the iframe — from Community Settings |
381
+ | `widget-url` | No | — | URL served inside the iframe — from Community Settings (iframe mode only) |
382
+ | `bundle-url` | No | — | URL to the widget IIFE bundle — triggers bundle mode (non-iframe) |
383
+ | `embed-token` | Access Key only | — | Short-lived JWT from your server. Required for Access Key Embed; absent for Public and Authenticated Embed |
384
+ | `auth-url` | Auth Embed only | — | Your backend auth endpoint — enables Authenticated Embed mode |
265
385
  | `auto-refresh` | No | `true` | Automatically refresh access token before expiry |
266
386
  | `debug` | No | `false` | Enable verbose console logging |
267
387
  | `eager` | No | — | Boolean — skip `IntersectionObserver`, init immediately on mount |
@@ -272,18 +392,20 @@ All attributes can be provided with or without the `data-` prefix.
272
392
  | `storage-prefix` | No | `returning-ai-widget` | `localStorage` key prefix — set per tenant to avoid collisions |
273
393
  | `retry-label` | No | `Retry` | Text for the retry button on the error screen |
274
394
  | `custom-data` | No | — | JSON string forwarded as `customData` in the token postMessage |
275
- | `auth-url` | No | — | Backend proxy endpoint for authenticated embed — if present, serverless auth is skipped entirely |
276
- | `data-email` | No | — | User identifier passed to auth (public embed only) |
395
+ | `data-email` | No | — | User identifier passed to auth (Public Embed only) |
277
396
  | `data-*` | No | — | Any additional `data-*` attributes are forwarded as `userIdentifiers` to the auth API |
278
397
 
398
+ > **Note:** The legacy `widget-id` attribute is still supported as a deprecated alias for `community-id`.
399
+
279
400
  ### DOM Events
280
401
 
281
402
  All events bubble and are `composed: true` (cross the Shadow DOM boundary).
282
403
 
283
404
  | Event | `detail` | Fired when |
284
405
  |-------|----------|-----------|
285
- | `rai-authenticated` | `{}` | Auth succeeded, before iframe mounts |
286
- | `rai-ready` | `{}` | `WIDGET_READY` received, loader hidden |
406
+ | `rai-authenticated` | `{}` | Auth succeeded, before widget mounts |
407
+ | `rai-ready` | `{}` | `WIDGET_READY` received, loader hidden (iframe mode) |
408
+ | `rai-mounted` | `{}` | Widget bundle mounted successfully (bundle mode) |
287
409
  | `rai-error` | `{ message }` | Auth failed after all retries |
288
410
  | `rai-logout` | `{}` | Widget logged out |
289
411
  | `rai-height-change` | `{ height }` | iframe resized (after debounce) |
@@ -299,7 +421,7 @@ After the SDK loads, `window.ReturningAIWidget` is available:
299
421
 
300
422
  ```javascript
301
423
  // Check the loaded version
302
- window.ReturningAIWidget.version // e.g. "1.0.0"
424
+ window.ReturningAIWidget.version // e.g. "1.0.3"
303
425
 
304
426
  // Reload the widget (re-runs auth flow)
305
427
  await window.ReturningAIWidget.reload()
@@ -365,27 +487,11 @@ npm run dev
365
487
 
366
488
  ### How the build works
367
489
 
368
- 1. **Entry**: `src/index.ts` — imports `StoreWidget`, registers the custom element, runs the bootstrap
490
+ 1. **Entry**: `src/index.ts` — imports all widget subclasses, registers the custom elements, runs the bootstrap
369
491
  2. **CSS inlining**: `src/styles/widget.css` is imported with Vite's `?inline` suffix, which converts it to a JavaScript string at build time — no separate CSS file is emitted
370
492
  3. **Output formats**: `iife` (Immediately Invoked Function Expression) for CDN/script-tag embeds → `dist/rai-widget.iife.js`; `es` (ES module) for bundlers and npm consumers → `dist/rai-widget.js`
371
493
  4. **Version injection**: `vite.config.ts` reads `version` from `package.json` and replaces the `__WIDGET_VERSION__` placeholder at build time
372
494
 
373
- ```
374
- src/index.ts
375
- ├── src/StoreWidget.ts
376
- │ └── src/BaseWidget.ts ← shared auth/iframe/Shadow DOM logic
377
- │ ├── src/types.ts
378
- │ ├── src/core/auth.ts
379
- │ │ └── src/core/storage.ts
380
- │ ├── src/core/storage.ts
381
- │ ├── src/core/postmessage.ts
382
- │ └── src/styles/widget.css [inlined as string]
383
- ├── src/ChannelWidget.ts └── src/BaseWidget.ts
384
- ├── src/MilestoneWidget.ts └── src/BaseWidget.ts
385
- ├── src/SocialWidget.ts └── src/BaseWidget.ts
386
- └── src/CurrencyWidget.ts └── src/BaseWidget.ts
387
- ```
388
-
389
495
  To release a new version, bump `version` in `package.json`, rebuild, and upload `dist/rai-widget.iife.js` to the CDN under the new version path. Run `npm publish` to push the ESM build to the npm registry (`prepublishOnly` handles the rebuild automatically).
390
496
 
391
497
  ---
@@ -396,12 +502,12 @@ To release a new version, bump `version` in `package.json`, rebuild, and upload
396
502
  rai-widget-sdks/
397
503
  ├── src/
398
504
  │ ├── types.ts # WidgetConfig, WidgetState, TokenData interfaces
399
- │ ├── BaseWidget.ts # Abstract HTMLElement subclass — Shadow Root, auth, iframe
505
+ │ ├── BaseWidget.ts # Abstract HTMLElement subclass — Shadow Root, auth, iframe/bundle
400
506
  │ ├── StoreWidget.ts # Extends BaseWidget; store micro-frontend URL builder
401
- │ ├── ChannelWidget.ts # Extends BaseWidget; channel SSR URL builder
402
- │ ├── MilestoneWidget.ts # Extends BaseWidget; milestone SSR URL builder
403
- │ ├── SocialWidget.ts # Extends BaseWidget; social SSR URL builder
404
- │ ├── CurrencyWidget.ts # Extends BaseWidget; currency-view SSR URL builder
507
+ │ ├── ChannelWidget.ts # Extends BaseWidget; channel URL builder
508
+ │ ├── MilestoneWidget.ts # Extends BaseWidget; milestone URL builder
509
+ │ ├── SocialWidget.ts # Extends BaseWidget; social URL builder
510
+ │ ├── CurrencyWidget.ts # Extends BaseWidget; currency-view URL builder
405
511
  │ ├── index.ts # Registers all 5 custom elements + IIFE bootstrap
406
512
  │ ├── jsx.d.ts # React JSX IntrinsicElements + Vue GlobalComponents type shims
407
513
  │ ├── vite-env.d.ts # Type declarations for ?inline imports + __WIDGET_VERSION__
@@ -1 +1 @@
1
- var RaiWidget=function(e){"use strict";var t=Object.defineProperty,r=(e,r,a)=>((e,r,a)=>r in e?t(e,r,{enumerable:!0,configurable:!0,writable:!0,value:a}):e[r]=a)(e,"symbol"!=typeof r?r+"":r,a);function a(e){return`${e.storagePrefix}-${e.widgetId}-auth`}function i(e){return`${e.storagePrefix}-${e.widgetId}-error-settings`}function s(e){try{localStorage.removeItem(a(e))}catch{}}function n(e){return!e||Date.now()>=e-6e4}function o(e,t,r,i){t.accessToken=r.accessToken,t.refreshToken=r.refreshToken,t.tokenFamily=r.tokenFamily??null;const s=Date.now();t.accessTokenExpiry=s+1e3*r.accessTokenTTL,t.refreshTokenExpiry=s+1e3*r.refreshTokenTTL,function(e,t){try{const r={refreshToken:t.refreshToken,tokenFamily:t.tokenFamily,refreshTokenExpiry:t.refreshTokenExpiry};localStorage.setItem(a(e),JSON.stringify(r))}catch{}}(e,t),e.autoRefresh&&i&&i()}async function l(e,t,r){const a=crypto.randomUUID(),i=Date.now(),s=e.maxRetries??3,n=e.retryDelay??500;for(let l=0;l<=s;l++){l>0&&await new Promise(e=>setTimeout(e,n*(1<<l-1)));let c=!1;try{const s=await fetch(`${e.apiUrl}/${e.widgetId}/auth/serverless`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({nonce:a,timestamp:i,widgetType:e.widgetType,userIdentifiers:e.userIdentifiers}),credentials:"include"});if(!s.ok){c=s.status>=400&&s.status<500;const e=await s.json().catch(()=>({error:"Authentication failed"}));throw new Error(e.error||`HTTP ${s.status}`)}const n=await s.json();if(!n.accessToken||!n.refreshToken)throw new Error("Invalid authentication response");return o(e,t,n,r),t.isAuthenticated=!0,!0}catch{if(c||l===s)return t.isAuthenticated=!1,!1}}return t.isAuthenticated=!1,!1}async function c(e,t,r){const a=e.maxRetries??3,i=e.retryDelay??500;for(let s=0;s<=a;s++){s>0&&await new Promise(e=>setTimeout(e,i*(1<<s-1)));let n=!1;try{const a=await fetch(e.authUrl,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"}});if(!a.ok)throw n=a.status>=400&&a.status<500,new Error(`HTTP ${a.status}`);const i=await a.json();if(!i.token)throw new Error("Invalid proxy auth response");const s=i.expiresIn??300;return t.accessToken=i.token,t.refreshToken=null,t.tokenFamily=null,t.accessTokenExpiry=Date.now()+1e3*s,t.refreshTokenExpiry=null,t.isAuthenticated=!0,e.autoRefresh&&r&&r(),!0}catch{if(n||s===a)return t.isAuthenticated=!1,!1}}return t.isAuthenticated=!1,!1}async function d(e,t,r,a){return t.isRefreshing?t.refreshPromise:!!t.refreshToken&&(t.isRefreshing=!0,t.refreshPromise=(async()=>{try{const i=await fetch(`${e.apiUrl}/${e.widgetId}/auth/refresh`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t.refreshToken}`},body:JSON.stringify({widgetType:e.widgetType})});if(!i.ok){if(401===i.status||403===i.status)return u(t),s(e),l(e,t,r);const a=await i.json().catch(()=>({error:"Token refresh failed"}));throw new Error(a.error||`HTTP ${i.status}`)}const n=await i.json();if(!n.accessToken||!n.refreshToken)throw new Error("Invalid refresh response");return o(e,t,n,r),a&&a(),!0}catch{return u(t),s(e),l(e,t,r)}finally{t.isRefreshing=!1,t.refreshPromise=null}})(),t.refreshPromise)}async function h(e,t){const r=function(e){try{const t=localStorage.getItem(i(e));if(!t)return null;const r=JSON.parse(t);return r.cachedAt&&Date.now()-r.cachedAt<36e5?r:(localStorage.removeItem(i(e)),null)}catch{return null}}(e);if(r)return r.configured&&(t.errorSettings={errorMessage:r.errorMessage,modalColor:r.modalColor,backgroundImage:r.backgroundImage}),t.errorSettings;try{const r=await fetch(`${e.apiUrl}/${e.widgetId}/auth/error-settings?widgetType=${e.widgetType}`,{method:"GET",headers:{"Content-Type":"application/json"}});if(!r.ok)return null;const a=await r.json();return function(e,t){try{localStorage.setItem(i(e),JSON.stringify({...t,cachedAt:Date.now()}))}catch{}}(e,a),a.configured&&(t.errorSettings={errorMessage:a.errorMessage,modalColor:a.modalColor,backgroundImage:a.backgroundImage}),t.errorSettings}catch{return null}}function u(e){e.accessToken=null,e.refreshToken=null,e.tokenFamily=null,e.accessTokenExpiry=null,e.refreshTokenExpiry=null,e.isAuthenticated=!1,e.isRefreshing=!1,e.refreshPromise=null,e.refreshTimer&&(clearTimeout(e.refreshTimer),e.refreshTimer=null),e.syncTimer&&(clearInterval(e.syncTimer),e.syncTimer=null)}function f(e,t,r){var a;if(t.accessToken)try{null==(a=r.contentWindow)||a.postMessage({type:"RETURNINGAI_WIDGET_TOKEN",value:{widgetId:e.widgetId,token:t.accessToken,...void 0!==e.customData&&{customData:e.customData}}},e.widgetDomain)}catch{}}function g(e,t,r,a,i,s,o,h){const u=function(e,t){let r=null;return{call(a){r&&clearTimeout(r),r=setTimeout(()=>{e(a),r=null},t)},cancel(){r&&(clearTimeout(r),r=null)}}}(e=>{a.style.height=`${e}px`,null==h||h("rai-height-change",{height:e})},e.heightDebounce??100),f=async r=>{var f;if(r.origin!==e.widgetDomain)return;if(!r.data||"string"!=typeof r.data.type)return;const{type:g,containerId:m,widgetId:p,payload:w}=r.data;switch(g){case"RETURNINGAI_WIDGET_REQUEST_TOKEN":try{const r=await async function(e,t,r){if(t.accessToken&&!n(t.accessTokenExpiry))return t.accessToken;if(e.authUrl){if(await c(e,t,r)&&t.accessToken)return t.accessToken;throw new Error("Unable to obtain valid token")}if(await d(e,t,r)&&t.accessToken)return t.accessToken;if(await l(e,t,r)&&t.accessToken)return t.accessToken;throw new Error("Unable to obtain valid token")}(e,t,i);null==(f=a.contentWindow)||f.postMessage({type:"RETURNINGAI_WIDGET_TOKEN",value:{token:r,widgetId:e.widgetId,...void 0!==e.customData&&{customData:e.customData}}},e.widgetDomain)}catch{}break;case"WIDGET_HEIGHT_UPDATE":{const e=Number(null==w?void 0:w.height);Number.isFinite(e)&&e>0&&u.call(e);break}case"WIDGET_READY":if(void 0!==p&&p!==e.widgetId)break;if(void 0!==m&&m!==e.container)break;a.classList.add("loaded"),o&&o(),null==h||h("rai-ready");break;case"WIDGET_ERROR":o&&o();break;case"WIDGET_LOGOUT":s&&await s()}};return window.addEventListener("message",f),()=>{window.removeEventListener("message",f),u.cancel()}}const m=new Set(["widget-id","widget-type","theme","container","width","height","api-url","widget-url","auto-refresh","debug","storage-prefix","max-retries","retry-delay","height-debounce","locale","custom-data","retry-label","auth-url"]);class p extends HTMLElement{constructor(){super(),r(this,"shadow"),r(this,"config"),r(this,"state",{accessToken:null,refreshToken:null,tokenFamily:null,accessTokenExpiry:null,refreshTokenExpiry:null,refreshTimer:null,syncTimer:null,iframe:null,isAuthenticated:!1,isRefreshing:!1,refreshPromise:null,errorSettings:null}),r(this,"loaderEl",null),r(this,"errorEl",null),r(this,"msgEl",null),r(this,"cleanupListener"),r(this,"intersectionObserver"),this.shadow=this.attachShadow({mode:"closed"})}connectedCallback(){this.config=function(e,t){const r=t=>e.getAttribute(t)??e.getAttribute(`data-${t}`)??"",a=(e,t)=>{const a=parseInt(r(e),10);return Number.isFinite(a)&&a>=0?a:t};let i,s=r("widget-url")||"https://widget.returningai.com";s.endsWith("store-widget")&&(s=`${s}/${r("widget-id")}/open-widget`);try{i=new URL(s).origin}catch{i=s}if(s.includes("store-widget")){const e=new URL(s);e.searchParams.set("color",r("theme")||"light"),s=e.toString()}const n=r("widget-id")||t||"";if(n&&!/^[a-zA-Z0-9_\-=]{8,}$/.test(n))throw new Error(`[rai-widget] Invalid widget-id format: "${n}"`);const o=r("container")||r("data-container")||`returning-ai-widget-${n}`,l={};Array.from(e.attributes).forEach(e=>{const t=e.name.toLowerCase();if(!t.startsWith("data-"))return;const r=t.slice(5);m.has(r)||(l[t]=e.value)});const c=(()=>{const e=r("custom-data");if(e)try{return JSON.parse(e)}catch{return}})();return{widgetId:n,widgetType:r("widget-type")||"store",theme:r("theme")||"light",container:o,width:r("width")||"100%",height:r("height")||"600px",apiUrl:r("api-url")||"https://sgtr-eks-widgets.genesiv.org",widgetUrl:s,widgetDomain:i,autoRefresh:"false"!==r("auto-refresh"),debug:"true"===r("debug"),storagePrefix:r("storage-prefix")||"returning-ai-widget",userIdentifiers:l,maxRetries:a("max-retries",3),retryDelay:a("retry-delay",500),heightDebounce:a("height-debounce",100),locale:r("locale")||void 0,customData:c,authUrl:r("auth-url")||void 0}}(this,void 0);const e=document.createElement("style");e.textContent=":host{display:block;position:relative;width:100%;height:100%}.rai-loader{position:absolute;top:0;left:0;display:flex;align-items:center;justify-content:center;width:100%;height:100%;background:var(--rai-loader-bg, #ffffff);border-radius:8px;z-index:10;transition:opacity .3s ease-out}.rai-loader.fade-out{opacity:0;pointer-events:none}.rai-error{display:none;position:absolute;top:0;left:0;width:100%;height:100%;align-items:center;justify-content:center;flex-direction:column;gap:12px;background:var(--rai-error-bg, #1a1a1a);border-radius:8px;padding:24px;box-sizing:border-box;text-align:center;color:var(--rai-error-text, #9ca3af);font-family:system-ui,-apple-system,sans-serif;z-index:10}.rai-error.visible{display:flex}iframe{display:block;border:none;opacity:0;transition:opacity .3s ease-in}iframe.loaded{opacity:1}.loader{position:relative;width:75px;height:100px}.loader__bar{position:absolute;bottom:0;width:10px;height:50%;background:var(--rai-accent, #000000);transform-origin:center bottom;box-shadow:1px 1px #0003}.loader__bar:nth-child(1){left:0;transform:scaleY(.2);animation:barUp1 4s infinite}.loader__bar:nth-child(2){left:15px;transform:scaleY(.4);animation:barUp2 4s infinite}.loader__bar:nth-child(3){left:30px;transform:scaleY(.6);animation:barUp3 4s infinite}.loader__bar:nth-child(4){left:45px;transform:scaleY(.8);animation:barUp4 4s infinite}.loader__bar:nth-child(5){left:60px;transform:scale(1);animation:barUp5 4s infinite}.loader__ball{position:absolute;bottom:10px;left:0;width:10px;height:10px;background:var(--rai-accent, #000000);border-radius:50%;animation:ball 4s infinite}@keyframes ball{0%{transform:translate(0)}5%{transform:translate(8px,-14px)}10%{transform:translate(15px,-10px)}17%{transform:translate(23px,-24px)}20%{transform:translate(30px,-20px)}27%{transform:translate(38px,-34px)}30%{transform:translate(45px,-30px)}37%{transform:translate(53px,-44px)}40%{transform:translate(60px,-40px)}50%{transform:translate(60px)}57%{transform:translate(53px,-14px)}60%{transform:translate(45px,-10px)}67%{transform:translate(37px,-24px)}70%{transform:translate(30px,-20px)}77%{transform:translate(22px,-34px)}80%{transform:translate(15px,-30px)}87%{transform:translate(7px,-44px)}90%{transform:translateY(-40px)}to{transform:translate(0)}}@keyframes barUp1{0%{transform:scaleY(.2)}40%{transform:scaleY(.2)}50%{transform:scale(1)}90%{transform:scale(1)}to{transform:scaleY(.2)}}@keyframes barUp2{0%{transform:scaleY(.4)}40%{transform:scaleY(.4)}50%{transform:scaleY(.8)}90%{transform:scaleY(.8)}to{transform:scaleY(.4)}}@keyframes barUp3{0%{transform:scaleY(.6)}to{transform:scaleY(.6)}}@keyframes barUp4{0%{transform:scaleY(.8)}40%{transform:scaleY(.8)}50%{transform:scaleY(.4)}90%{transform:scaleY(.4)}to{transform:scaleY(.8)}}@keyframes barUp5{0%{transform:scale(1)}40%{transform:scale(1)}50%{transform:scaleY(.2)}90%{transform:scaleY(.2)}to{transform:scale(1)}}#loading-square{width:75px;aspect-ratio:1;display:flex;color:var(--rai-accent, #000000);background:linear-gradient(currentColor 0 0) right / 51% 100%,linear-gradient(currentColor 0 0) bottom / 100% 51%;background-repeat:no-repeat;animation:l16-0 2s infinite linear .25s}#loading-square>div{width:50%;height:50%;background:currentColor;animation:l16-1 .5s infinite linear}@keyframes l16-0{0%,12.49%{transform:rotate(0)}12.5%,37.49%{transform:rotate(90deg)}37.5%,62.49%{transform:rotate(180deg)}62.5%,87.49%{transform:rotate(270deg)}87.5%,to{transform:rotate(360deg)}}@keyframes l16-1{0%{transform:perspective(80px) rotate3d(-1,-1,0,0)}80%,to{transform:perspective(80px) rotate3d(-1,-1,0,-180deg)}}#loading-circle{width:75px;aspect-ratio:1;display:grid;grid:50%/50%;color:var(--rai-accent, #000000);border-radius:50%;--_g: no-repeat linear-gradient(currentColor 0 0);background:var(--_g),var(--_g),var(--_g);background-size:50.1% 50.1%;animation:l9-0 1.5s infinite steps(1) alternate,l9-0-0 3s infinite steps(1) alternate}#loading-circle>div{background:var(--rai-text4, #6b7280);border-top-left-radius:100px;transform:perspective(150px) rotateY(0) rotateX(0);transform-origin:bottom right;animation:l9-1 1.5s infinite linear alternate}@keyframes l9-0{0%{background-position:0 100%,100% 100%,100% 0}33%{background-position:100% 100%,100% 100%,100% 0}66%{background-position:100% 0,100% 0,100% 0}}@keyframes l9-0-0{0%{transform:scaleX(1) rotate(0)}50%{transform:scaleX(-1) rotate(-90deg)}}@keyframes l9-1{16.5%{transform:perspective(150px) rotateX(-90deg) rotateY(0) rotateX(0);filter:grayscale(.8)}33%{transform:perspective(150px) rotateX(-180deg) rotateY(0) rotateX(0)}66%{transform:perspective(150px) rotateX(-180deg) rotateY(-180deg) rotateX(0)}to{transform:perspective(150px) rotateX(-180deg) rotateY(-180deg) rotateX(-180deg);filter:grayscale(.8)}}.rai-retry-btn{padding:8px 20px;border:none;border-radius:6px;background:var(--rai-accent, #000000);color:var(--rai-error-bg, #ffffff);font-size:14px;font-family:system-ui,-apple-system,sans-serif;cursor:pointer}.rai-retry-btn:hover{opacity:.85}",this.shadow.appendChild(e);const t=this.config.theme,r="dark"===t?"#ffffff":"#000000",a="dark"===t?"#9ca3af":"#6b7280",i="dark"===t?"#1a1a1a":"#ffffff";this.style.setProperty("--rai-accent",r),this.style.setProperty("--rai-text4",a),this.style.setProperty("--rai-loader-bg",i),this.style.setProperty("--rai-error-bg",i),this.renderShell(),this.hasAttribute("eager")?this.init():(this.intersectionObserver=new IntersectionObserver(e=>{e[0].isIntersecting&&(this.intersectionObserver.disconnect(),this.intersectionObserver=void 0,this.init())}),this.intersectionObserver.observe(this))}disconnectedCallback(){var e;null==(e=this.intersectionObserver)||e.disconnect(),this.cleanupListener&&this.cleanupListener(),u(this.state)}emit(e,t){this.dispatchEvent(new CustomEvent(e,{bubbles:!0,composed:!0,detail:t??{}}))}renderShell(){this.loaderEl=document.createElement("div"),this.loaderEl.className="rai-loader",this.loaderEl.appendChild(this.createDefaultLoader()),this.shadow.appendChild(this.loaderEl),this.errorEl=document.createElement("div"),this.errorEl.className="rai-error",this.msgEl=document.createElement("span"),this.msgEl.className="rai-error-msg",this.msgEl.textContent="Authentication failed. Please try again later.",this.errorEl.appendChild(this.msgEl);const e=this.getAttribute("retry-label")||this.getAttribute("data-retry-label")||"Retry",t=document.createElement("button");t.className="rai-retry-btn",t.textContent=e,t.addEventListener("click",()=>this.reload()),this.errorEl.appendChild(t),this.shadow.appendChild(this.errorEl)}createDefaultLoader(){const e=document.createElement("div");e.className="loader";for(let r=0;r<5;r++){const t=document.createElement("div");t.className="loader__bar",e.appendChild(t)}const t=document.createElement("div");return t.className="loader__ball",e.appendChild(t),e}hideLoader(){this.loaderEl&&(this.loaderEl.classList.add("fade-out"),setTimeout(()=>{var e;return null==(e=this.loaderEl)?void 0:e.remove()},300),this.loaderEl=null)}showError(){var e,t;this.hideLoader(),this.errorEl&&((null==(e=this.state.errorSettings)?void 0:e.errorMessage)&&this.msgEl&&(this.msgEl.textContent=this.state.errorSettings.errorMessage),this.errorEl.classList.add("visible")),this.emit("rai-error",{message:(null==(t=this.msgEl)?void 0:t.textContent)??"Authentication failed"})}async init(){if(!this.config.widgetId)return void this.showError();if(await h(this.config,this.state),this.config.authUrl){return void(await c(this.config,this.state,()=>this.scheduleRefresh())?(this.emit("rai-authenticated"),this.createIframe()):this.showError())}if(function(e,t){try{const r=localStorage.getItem(a(e));if(!r)return!1;const i=JSON.parse(r);return i.refreshToken&&!n(i.refreshTokenExpiry)?(t.refreshToken=i.refreshToken,t.tokenFamily=i.tokenFamily,t.refreshTokenExpiry=i.refreshTokenExpiry,!0):(s(e),!1)}catch{return!1}}(this.config,this.state)){return await d(this.config,this.state,()=>this.scheduleRefresh())?(this.state.isAuthenticated=!0,this.emit("rai-authenticated"),void this.createIframe()):void this.showError()}await l(this.config,this.state,()=>this.scheduleRefresh())?(this.emit("rai-authenticated"),this.createIframe()):this.showError()}createIframe(){const e=document.createElement("iframe");e.src=this.buildWidgetUrl(this.config),e.allow="clipboard-write",e.setAttribute("sandbox","allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"),e.frameBorder="0",e.scrolling="no",e.style.width=this.config.width,e.style.height=this.config.height,e.style.border="none",e.style.display="block",e.onload=()=>{f(this.config,this.state,e),this.schedulePeriodicSync(e)},e.onerror=()=>{this.showError()},this.state.iframe=e,this.shadow.appendChild(e),this.cleanupListener=g(this.config,this.state,this.shadow,e,()=>this.scheduleRefresh(),()=>this.logoutAndClear(),()=>this.hideLoader(),(e,t)=>this.emit(e,t))}scheduleRefresh(){if(this.state.refreshTimer&&clearTimeout(this.state.refreshTimer),!this.state.accessTokenExpiry)return;const e=this.state.accessTokenExpiry-Date.now()-6e4,t=()=>{this.state.iframe&&f(this.config,this.state,this.state.iframe)},r=async()=>{if(this.config.authUrl){await c(this.config,this.state,()=>this.scheduleRefresh())&&t()}else await d(this.config,this.state,()=>this.scheduleRefresh(),t)};e>0?this.state.refreshTimer=setTimeout(r,e):r()}schedulePeriodicSync(e){this.state.syncTimer&&clearInterval(this.state.syncTimer),this.state.syncTimer=setInterval(()=>{this.state.accessToken&&f(this.config,this.state,e)},12e4)}async logoutAndClear(){await async function(e,t){if(t.accessToken)try{await fetch(`${e.apiUrl}/${e.widgetId}/auth/logout`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t.accessToken}`},body:JSON.stringify({refreshToken:t.refreshToken})})}catch{}u(t),s(e)}(this.config,this.state),this.shadow.querySelectorAll("iframe").forEach(e=>e.remove()),this.emit("rai-logout")}async reload(){u(this.state),this.shadow.querySelectorAll("iframe").forEach(e=>e.remove()),await this.init()}async logoutPublic(){await this.logoutAndClear()}isAuthenticated(){return this.state.isAuthenticated&&null!==this.state.accessToken}getTokenInfo(){return{hasAccessToken:!!this.state.accessToken,hasRefreshToken:!!this.state.refreshToken,accessTokenExpiry:this.state.accessTokenExpiry?new Date(this.state.accessTokenExpiry):null,refreshTokenExpiry:this.state.refreshTokenExpiry?new Date(this.state.refreshTokenExpiry):null,isAccessTokenValid:!!this.state.accessToken&&!n(this.state.accessTokenExpiry),isRefreshTokenValid:!!this.state.refreshToken&&!n(this.state.refreshTokenExpiry)}}}class w extends p{buildWidgetUrl(e){const t=new URL(e.widgetUrl);return t.searchParams.set("color",e.theme),t.searchParams.set("containerId",e.container),t.searchParams.set("connectType","simple"),t.searchParams.set("mode","private"),e.locale&&t.searchParams.set("locale",e.locale),t.toString()}}class y extends p{buildWidgetUrl(e){const t=e.widgetUrl.endsWith("channel-widget")?`${e.widgetUrl}/${e.widgetId}`:e.widgetUrl;if(!e.locale)return t;try{const r=new URL(t);return r.searchParams.set("locale",e.locale),r.toString()}catch{return`${t}?locale=${encodeURIComponent(e.locale)}`}}}class b extends p{buildWidgetUrl(e){const t=e.widgetUrl.endsWith("milestone-widget")?`${e.widgetUrl}/${e.widgetId}`:e.widgetUrl;if(!e.locale)return t;try{const r=new URL(t);return r.searchParams.set("locale",e.locale),r.toString()}catch{return`${t}?locale=${encodeURIComponent(e.locale)}`}}}class T extends p{buildWidgetUrl(e){const t=e.widgetUrl.endsWith("social-widget")?`${e.widgetUrl}/${e.widgetId}`:e.widgetUrl;if(!e.locale)return t;try{const r=new URL(t);return r.searchParams.set("locale",e.locale),r.toString()}catch{return`${t}?locale=${encodeURIComponent(e.locale)}`}}}class k extends p{buildWidgetUrl(e){const t=e.widgetUrl.endsWith("currency-overview-widget")?`${e.widgetUrl}/${e.widgetId}`:e.widgetUrl;if(!e.locale)return t;try{const r=new URL(t);return r.searchParams.set("locale",e.locale),r.toString()}catch{return`${t}?locale=${encodeURIComponent(e.locale)}`}}}console.log("[rai-widget] v1.0.3");const x=[["rai-widget",w],["rai-channel-widget",y],["rai-milestone-widget",b],["rai-social-widget",T],["rai-currency-widget",k]];for(const[S,A]of x)customElements.get(S)||customElements.define(S,A);const v={store:w,channel:y,milestone:b,social:T,"currency-view":k},E=x.map(([e])=>e).join(", ");function I(){var e;const t=((null==(e=document.currentScript)?void 0:e.hasAttribute("data-widget-id"))?document.currentScript:null)||document.querySelector("script[data-widget-id]");if(!t)return;const r=t.getAttribute("data-widget-id");if(!r)return;const a=t.getAttribute("data-container")||`returning-ai-widget-${r}`,i=document.getElementById(a);if(!i)return;"static"===getComputedStyle(i).position&&(i.style.position="relative");const s=t.getAttribute("data-widget-type")??"store",n=new(v[s]??w);Array.from(t.attributes).forEach(e=>{n.setAttribute(e.name,e.value)}),i.appendChild(n)}function U(){const e=(()=>{var e;const t=((null==(e=document.currentScript)?void 0:e.hasAttribute("data-widget-id"))?document.currentScript:null)||document.querySelector("script[data-widget-id]");if(!t)return null;const r=t.getAttribute("data-container")||`returning-ai-widget-${t.getAttribute("data-widget-id")}`;return document.getElementById(r)})(),t=null==e?void 0:e.querySelector(E);window.ReturningAIWidget={version:"1.0.3",reload:()=>(null==t?void 0:t.reload())??Promise.resolve(),logout:()=>(null==t?void 0:t.logoutPublic())??Promise.resolve(),isAuthenticated:()=>(null==t?void 0:t.isAuthenticated())??!1,getTokenInfo:()=>(null==t?void 0:t.getTokenInfo())??{}}}return"loading"===document.readyState?document.addEventListener("DOMContentLoaded",I):I(),"loading"===document.readyState?document.addEventListener("DOMContentLoaded",U):U(),e.ChannelWidget=y,e.CurrencyWidget=k,e.MilestoneWidget=b,e.SocialWidget=T,e.StoreWidget=w,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),e}({});
1
+ var RaiWidget=function(e){"use strict";var t=Object.defineProperty,r=(e,r,i)=>((e,r,i)=>r in e?t(e,r,{enumerable:!0,configurable:!0,writable:!0,value:i}):e[r]=i)(e,"symbol"!=typeof r?r+"":r,i);function i(e){return`${e.storagePrefix}-${e.widgetId}-auth`}function a(e){return`${e.storagePrefix}-${e.widgetId}-error-settings`}function s(e){try{localStorage.removeItem(i(e))}catch{}}function n(e){return!e||Date.now()>=e-6e4}function o(e,t,r,a){t.accessToken=r.accessToken,t.refreshToken=r.refreshToken,t.tokenFamily=r.tokenFamily??null;const s=Date.now();t.accessTokenExpiry=s+1e3*r.accessTokenTTL,t.refreshTokenExpiry=s+1e3*r.refreshTokenTTL,function(e,t){try{const r={refreshToken:t.refreshToken,tokenFamily:t.tokenFamily,refreshTokenExpiry:t.refreshTokenExpiry};localStorage.setItem(i(e),JSON.stringify(r))}catch{}}(e,t),e.autoRefresh&&a&&a()}async function l(e,t,r){const i=crypto.randomUUID(),a=Date.now(),s=e.maxRetries??3,n=e.retryDelay??500;for(let l=0;l<=s;l++){l>0&&await new Promise(e=>setTimeout(e,n*(1<<l-1)));let c=!1;try{const s=await fetch(`${e.apiUrl}/${e.widgetId}/auth/serverless`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({nonce:i,timestamp:a,widgetType:e.widgetType,userIdentifiers:e.userIdentifiers}),credentials:"include"});if(!s.ok){c=s.status>=400&&s.status<500;const e=await s.json().catch(()=>({error:"Authentication failed"}));throw new Error(e.error||`HTTP ${s.status}`)}const n=await s.json();if(!n.accessToken||!n.refreshToken)throw new Error("Invalid authentication response");return o(e,t,n,r),t.isAuthenticated=!0,!0}catch{if(c||l===s)return t.isAuthenticated=!1,!1}}return t.isAuthenticated=!1,!1}async function c(e,t,r){const i=e.maxRetries??3,a=e.retryDelay??500;for(let s=0;s<=i;s++){s>0&&await new Promise(e=>setTimeout(e,a*(1<<s-1)));let n=!1;try{const i=await fetch(e.authUrl,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"}});if(!i.ok)throw n=i.status>=400&&i.status<500,new Error(`HTTP ${i.status}`);const a=await i.json();if(!a.token)throw new Error("Invalid proxy auth response");const s=a.expiresIn??300;return t.accessToken=a.token,t.refreshToken=null,t.tokenFamily=null,t.accessTokenExpiry=Date.now()+1e3*s,t.refreshTokenExpiry=null,t.isAuthenticated=!0,e.autoRefresh&&r&&r(),!0}catch{if(n||s===i)return t.isAuthenticated=!1,!1}}return t.isAuthenticated=!1,!1}async function d(e,t,r,i){return t.isRefreshing?t.refreshPromise:!!t.refreshToken&&(t.isRefreshing=!0,t.refreshPromise=(async()=>{try{const a=await fetch(`${e.apiUrl}/${e.widgetId}/auth/refresh`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t.refreshToken}`},body:JSON.stringify({widgetType:e.widgetType})});if(!a.ok){if(401===a.status||403===a.status)return u(t),s(e),l(e,t,r);const i=await a.json().catch(()=>({error:"Token refresh failed"}));throw new Error(i.error||`HTTP ${a.status}`)}const n=await a.json();if(!n.accessToken||!n.refreshToken)throw new Error("Invalid refresh response");return o(e,t,n,r),i&&i(),!0}catch{return u(t),s(e),l(e,t,r)}finally{t.isRefreshing=!1,t.refreshPromise=null}})(),t.refreshPromise)}async function h(e,t){const r=function(e){try{const t=localStorage.getItem(a(e));if(!t)return null;const r=JSON.parse(t);return r.cachedAt&&Date.now()-r.cachedAt<36e5?r:(localStorage.removeItem(a(e)),null)}catch{return null}}(e);if(r)return r.configured&&(t.errorSettings={errorMessage:r.errorMessage,modalColor:r.modalColor,backgroundImage:r.backgroundImage}),t.errorSettings;try{const r=await fetch(`${e.apiUrl}/${e.widgetId}/auth/error-settings?widgetType=${e.widgetType}`,{method:"GET",headers:{"Content-Type":"application/json"}});if(!r.ok)return null;const i=await r.json();return function(e,t){try{localStorage.setItem(a(e),JSON.stringify({...t,cachedAt:Date.now()}))}catch{}}(e,i),i.configured&&(t.errorSettings={errorMessage:i.errorMessage,modalColor:i.modalColor,backgroundImage:i.backgroundImage}),t.errorSettings}catch{return null}}function u(e){e.accessToken=null,e.refreshToken=null,e.tokenFamily=null,e.accessTokenExpiry=null,e.refreshTokenExpiry=null,e.isAuthenticated=!1,e.isRefreshing=!1,e.refreshPromise=null,e.refreshTimer&&(clearTimeout(e.refreshTimer),e.refreshTimer=null),e.syncTimer&&(clearInterval(e.syncTimer),e.syncTimer=null)}function f(e,t,r){var i;if(t.accessToken)try{null==(i=r.contentWindow)||i.postMessage({type:"RETURNINGAI_WIDGET_TOKEN",value:{widgetId:e.widgetId,token:t.accessToken,...void 0!==e.customData&&{customData:e.customData}}},e.widgetDomain)}catch{}}function m(e,t,r,i,a,s,o,h){const u=function(e,t){let r=null;return{call(i){r&&clearTimeout(r),r=setTimeout(()=>{e(i),r=null},t)},cancel(){r&&(clearTimeout(r),r=null)}}}(e=>{i.style.height=`${e}px`,null==h||h("rai-height-change",{height:e})},e.heightDebounce??100),f=async r=>{var f;if(r.origin!==e.widgetDomain)return;if(!r.data||"string"!=typeof r.data.type)return;const{type:m,containerId:g,widgetId:p,payload:y}=r.data;switch(m){case"RETURNINGAI_WIDGET_REQUEST_TOKEN":try{const r=await async function(e,t,r){if(t.accessToken&&!n(t.accessTokenExpiry))return t.accessToken;if(e.authUrl){if(await c(e,t,r)&&t.accessToken)return t.accessToken;throw new Error("Unable to obtain valid token")}if(await d(e,t,r)&&t.accessToken)return t.accessToken;if(await l(e,t,r)&&t.accessToken)return t.accessToken;throw new Error("Unable to obtain valid token")}(e,t,a);null==(f=i.contentWindow)||f.postMessage({type:"RETURNINGAI_WIDGET_TOKEN",value:{token:r,widgetId:e.widgetId,...void 0!==e.customData&&{customData:e.customData}}},e.widgetDomain)}catch{}break;case"WIDGET_HEIGHT_UPDATE":{const e=Number(null==y?void 0:y.height);Number.isFinite(e)&&e>0&&u.call(e);break}case"WIDGET_READY":if(void 0!==p&&p!==e.widgetId)break;if(void 0!==g&&g!==e.container)break;i.classList.add("loaded"),o&&o(),null==h||h("rai-ready");break;case"WIDGET_ERROR":o&&o();break;case"WIDGET_LOGOUT":s&&await s()}};return window.addEventListener("message",f),()=>{window.removeEventListener("message",f),u.cancel()}}const g=new Set(["community-id","channel-id","widget-id","widget-type","theme","container","width","height","api-url","widget-url","auto-refresh","debug","storage-prefix","max-retries","retry-delay","height-debounce","locale","custom-data","retry-label","auth-url","bundle-url","embed-token"]),p={store:"RaiStoreWidget",channel:"RaiChannelWidget",social:"RaiSocialWidget",milestone:"RaiMilestoneWidget","currency-view":"RaiCurrencyWidget"},y={"RAI-STORE-WIDGET":"store","RAI-CHANNEL-WIDGET":"channel","RAI-MILESTONE-WIDGET":"milestone","RAI-SOCIAL-WIDGET":"social","RAI-CURRENCY-WIDGET":"currency-view","RAI-WIDGET":"store"};class w extends HTMLElement{constructor(){super(),r(this,"shadow"),r(this,"config"),r(this,"state",{accessToken:null,refreshToken:null,tokenFamily:null,accessTokenExpiry:null,refreshTokenExpiry:null,refreshTimer:null,syncTimer:null,iframe:null,isAuthenticated:!1,isRefreshing:!1,refreshPromise:null,errorSettings:null}),r(this,"loaderEl",null),r(this,"errorEl",null),r(this,"msgEl",null),r(this,"cleanupListener"),r(this,"intersectionObserver"),r(this,"themeObserver"),this.shadow=this.attachShadow({mode:"closed"})}connectedCallback(){this.config=function(e,t){const r=t=>e.getAttribute(t)??e.getAttribute(`data-${t}`)??"",i=(e,t)=>{const i=parseInt(r(e),10);return Number.isFinite(i)&&i>=0?i:t},a=!!r("community-id"),s=r("community-id")||r("widget-id")||t||"";if(s&&!/^[a-zA-Z0-9_\-=]{8,}$/.test(s))throw new Error(`[rai-widget] Invalid community-id: "${s}"`);const n=r("channel-id")||void 0;if(n&&!/^[a-zA-Z0-9_\-=]{8,}$/.test(n))throw new Error(`[rai-widget] Invalid channel-id: "${n}"`);const o=r("widget-type")||y[e.tagName]||"store";let l;l=a?"store"===o?s:"channel"===o?n?btoa(n):s:s?btoa(s):s:s;let c,d=r("widget-url")||"https://widget.returningai.com";d.endsWith("store-widget")&&(d=`${d}/${s}/open-widget`);try{c=new URL(d).origin}catch{c=d}if(d.includes("store-widget")){const e=new URL(d);e.searchParams.set("color",r("theme")||"light"),d=e.toString()}const h=r("container")||r("data-container")||`returning-ai-widget-${s}`,u={};Array.from(e.attributes).forEach(e=>{const t=e.name.toLowerCase();if(!t.startsWith("data-"))return;const r=t.slice(5);g.has(r)||(u[t]=e.value)});const f=(()=>{const e=r("custom-data");if(e)try{return JSON.parse(e)}catch{return}})();return{communityId:s,channelId:n,widgetId:l,widgetType:o,theme:r("theme")||"light",container:h,width:r("width")||"100%",height:r("height")||"600px",apiUrl:r("api-url")||"https://sgtr-eks-widgets.genesiv.org",widgetUrl:d,widgetDomain:c,autoRefresh:"false"!==r("auto-refresh"),debug:"true"===r("debug"),storagePrefix:r("storage-prefix")||"returning-ai-widget",userIdentifiers:u,maxRetries:i("max-retries",3),retryDelay:i("retry-delay",500),heightDebounce:i("height-debounce",100),locale:r("locale")||void 0,customData:f,authUrl:r("auth-url")||void 0,bundleUrl:r("bundle-url")||void 0,embedToken:r("embed-token")||void 0}}(this,void 0);const e=document.createElement("style");e.textContent=":host{display:block;position:relative;width:100%;height:100%}.rai-loader{position:absolute;top:0;left:0;display:flex;align-items:center;justify-content:center;width:100%;height:100%;background:var(--rai-loader-bg, #ffffff);border-radius:8px;z-index:10;transition:opacity .3s ease-out}.rai-loader.fade-out{opacity:0;pointer-events:none}.rai-error{display:none;position:absolute;top:0;left:0;width:100%;height:100%;align-items:center;justify-content:center;flex-direction:column;gap:12px;background:var(--rai-error-bg, #1a1a1a);border-radius:8px;padding:24px;box-sizing:border-box;text-align:center;color:var(--rai-error-text, #9ca3af);font-family:system-ui,-apple-system,sans-serif;z-index:10}.rai-error.visible{display:flex}iframe{display:block;border:none;opacity:0;transition:opacity .3s ease-in}iframe.loaded{opacity:1}.rai-sdk-loader{position:relative;width:75px;height:100px}.rai-sdk-loader__bar{position:absolute;bottom:0;width:10px;height:50%;background:var(--rai-accent, #000000);transform-origin:center bottom;box-shadow:1px 1px #0003}.rai-sdk-loader__bar:nth-child(1){left:0;transform:scaleY(.2);animation:rai-sdk-barUp1 4s infinite}.rai-sdk-loader__bar:nth-child(2){left:15px;transform:scaleY(.4);animation:rai-sdk-barUp2 4s infinite}.rai-sdk-loader__bar:nth-child(3){left:30px;transform:scaleY(.6);animation:rai-sdk-barUp3 4s infinite}.rai-sdk-loader__bar:nth-child(4){left:45px;transform:scaleY(.8);animation:rai-sdk-barUp4 4s infinite}.rai-sdk-loader__bar:nth-child(5){left:60px;transform:scale(1);animation:rai-sdk-barUp5 4s infinite}.rai-sdk-loader__ball{position:absolute;bottom:10px;left:0;width:10px;height:10px;background:var(--rai-accent, #000000);border-radius:50%;animation:rai-sdk-ball 4s infinite}@keyframes rai-sdk-ball{0%{transform:translate(0)}5%{transform:translate(8px,-14px)}10%{transform:translate(15px,-10px)}17%{transform:translate(23px,-24px)}20%{transform:translate(30px,-20px)}27%{transform:translate(38px,-34px)}30%{transform:translate(45px,-30px)}37%{transform:translate(53px,-44px)}40%{transform:translate(60px,-40px)}50%{transform:translate(60px)}57%{transform:translate(53px,-14px)}60%{transform:translate(45px,-10px)}67%{transform:translate(37px,-24px)}70%{transform:translate(30px,-20px)}77%{transform:translate(22px,-34px)}80%{transform:translate(15px,-30px)}87%{transform:translate(7px,-44px)}90%{transform:translateY(-40px)}to{transform:translate(0)}}@keyframes rai-sdk-barUp1{0%{transform:scaleY(.2)}40%{transform:scaleY(.2)}50%{transform:scale(1)}90%{transform:scale(1)}to{transform:scaleY(.2)}}@keyframes rai-sdk-barUp2{0%{transform:scaleY(.4)}40%{transform:scaleY(.4)}50%{transform:scaleY(.8)}90%{transform:scaleY(.8)}to{transform:scaleY(.4)}}@keyframes rai-sdk-barUp3{0%{transform:scaleY(.6)}to{transform:scaleY(.6)}}@keyframes rai-sdk-barUp4{0%{transform:scaleY(.8)}40%{transform:scaleY(.8)}50%{transform:scaleY(.4)}90%{transform:scaleY(.4)}to{transform:scaleY(.8)}}@keyframes rai-sdk-barUp5{0%{transform:scale(1)}40%{transform:scale(1)}50%{transform:scaleY(.2)}90%{transform:scaleY(.2)}to{transform:scale(1)}}#loading-square{width:75px;aspect-ratio:1;display:flex;color:var(--rai-accent, #000000);background:linear-gradient(currentColor 0 0) right / 51% 100%,linear-gradient(currentColor 0 0) bottom / 100% 51%;background-repeat:no-repeat;animation:l16-0 2s infinite linear .25s}#loading-square>div{width:50%;height:50%;background:currentColor;animation:l16-1 .5s infinite linear}@keyframes l16-0{0%,12.49%{transform:rotate(0)}12.5%,37.49%{transform:rotate(90deg)}37.5%,62.49%{transform:rotate(180deg)}62.5%,87.49%{transform:rotate(270deg)}87.5%,to{transform:rotate(360deg)}}@keyframes l16-1{0%{transform:perspective(80px) rotate3d(-1,-1,0,0)}80%,to{transform:perspective(80px) rotate3d(-1,-1,0,-180deg)}}#loading-circle{width:75px;aspect-ratio:1;display:grid;grid:50%/50%;color:var(--rai-accent, #000000);border-radius:50%;--_g: no-repeat linear-gradient(currentColor 0 0);background:var(--_g),var(--_g),var(--_g);background-size:50.1% 50.1%;animation:l9-0 1.5s infinite steps(1) alternate,l9-0-0 3s infinite steps(1) alternate}#loading-circle>div{background:var(--rai-text4, #6b7280);border-top-left-radius:100px;transform:perspective(150px) rotateY(0) rotateX(0);transform-origin:bottom right;animation:l9-1 1.5s infinite linear alternate}@keyframes l9-0{0%{background-position:0 100%,100% 100%,100% 0}33%{background-position:100% 100%,100% 100%,100% 0}66%{background-position:100% 0,100% 0,100% 0}}@keyframes l9-0-0{0%{transform:scaleX(1) rotate(0)}50%{transform:scaleX(-1) rotate(-90deg)}}@keyframes l9-1{16.5%{transform:perspective(150px) rotateX(-90deg) rotateY(0) rotateX(0);filter:grayscale(.8)}33%{transform:perspective(150px) rotateX(-180deg) rotateY(0) rotateX(0)}66%{transform:perspective(150px) rotateX(-180deg) rotateY(-180deg) rotateX(0)}to{transform:perspective(150px) rotateX(-180deg) rotateY(-180deg) rotateX(-180deg);filter:grayscale(.8)}}.rai-retry-btn{padding:8px 20px;border:none;border-radius:6px;background:var(--rai-accent, #000000);color:var(--rai-error-bg, #ffffff);font-size:14px;font-family:system-ui,-apple-system,sans-serif;cursor:pointer}.rai-retry-btn:hover{opacity:.85}",this.shadow.appendChild(e);const t=this.config.theme,r="dark"===t?"#ffffff":"#000000",i="dark"===t?"#9ca3af":"#6b7280",a="dark"===t?"#1a1a1a":"#ffffff";this.style.width=this.config.width,this.style.height=this.config.height,this.style.setProperty("--rai-accent",r),this.style.setProperty("--rai-text4",i),this.style.setProperty("--rai-loader-bg",a),this.style.setProperty("--rai-error-bg",a),this.renderShell(),this.hasAttribute("eager")?this.init():(this.intersectionObserver=new IntersectionObserver(e=>{e[0].isIntersecting&&(this.intersectionObserver.disconnect(),this.intersectionObserver=void 0,this.init())}),this.intersectionObserver.observe(this))}disconnectedCallback(){var e,t;null==(e=this.intersectionObserver)||e.disconnect(),null==(t=this.themeObserver)||t.disconnect(),this.cleanupListener&&this.cleanupListener(),u(this.state)}emit(e,t){this.dispatchEvent(new CustomEvent(e,{bubbles:!0,composed:!0,detail:t??{}}))}renderShell(){this.loaderEl=document.createElement("div"),this.loaderEl.className="rai-loader",this.loaderEl.appendChild(this.createDefaultLoader()),this.shadow.appendChild(this.loaderEl),this.errorEl=document.createElement("div"),this.errorEl.className="rai-error",this.msgEl=document.createElement("span"),this.msgEl.className="rai-error-msg",this.msgEl.textContent="Authentication failed. Please try again later.",this.errorEl.appendChild(this.msgEl);const e=this.getAttribute("retry-label")||this.getAttribute("data-retry-label")||"Retry",t=document.createElement("button");t.className="rai-retry-btn",t.textContent=e,t.addEventListener("click",()=>this.reload()),this.errorEl.appendChild(t),this.shadow.appendChild(this.errorEl)}createDefaultLoader(){const e=document.createElement("div");e.className="rai-sdk-loader";for(let r=0;r<5;r++){const t=document.createElement("div");t.className="rai-sdk-loader__bar",e.appendChild(t)}const t=document.createElement("div");return t.className="rai-sdk-loader__ball",e.appendChild(t),e}hideLoader(){this.loaderEl&&(this.loaderEl.classList.add("fade-out"),setTimeout(()=>{var e;return null==(e=this.loaderEl)?void 0:e.remove()},300),this.loaderEl=null)}showError(){var e,t;this.hideLoader(),this.errorEl&&((null==(e=this.state.errorSettings)?void 0:e.errorMessage)&&this.msgEl&&(this.msgEl.textContent=this.state.errorSettings.errorMessage),this.errorEl.classList.add("visible")),this.emit("rai-error",{message:(null==(t=this.msgEl)?void 0:t.textContent)??"Authentication failed"})}async init(){if(!this.config.communityId)return void this.showError();if(await h(this.config,this.state),this.config.embedToken){if(!(await async function(e){try{return(await fetch(`${e.apiUrl}/v2/api/widget-access-keys/validate`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({embedToken:e.embedToken})})).ok}catch{return!1}}(this.config)))return void this.showError()}const e=()=>{this.config.bundleUrl?this.mountWidget():this.createIframe()};if(this.config.authUrl){return void(await c(this.config,this.state,()=>this.scheduleRefresh())?(this.emit("rai-authenticated"),e()):this.showError())}if(function(e,t){try{const r=localStorage.getItem(i(e));if(!r)return!1;const a=JSON.parse(r);return a.refreshToken&&!n(a.refreshTokenExpiry)?(t.refreshToken=a.refreshToken,t.tokenFamily=a.tokenFamily,t.refreshTokenExpiry=a.refreshTokenExpiry,!0):(s(e),!1)}catch{return!1}}(this.config,this.state)){return await d(this.config,this.state,()=>this.scheduleRefresh())?(this.state.isAuthenticated=!0,this.emit("rai-authenticated"),void e()):void this.showError()}await l(this.config,this.state,()=>this.scheduleRefresh())?(this.emit("rai-authenticated"),e()):this.showError()}createIframe(){const e=document.createElement("iframe");e.src=this.buildWidgetUrl(this.config),e.allow="clipboard-write",e.setAttribute("sandbox","allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"),e.frameBorder="0",e.scrolling="no",e.style.width=this.config.width,e.style.height=this.config.height,e.style.border="none",e.style.display="block",e.onload=()=>{f(this.config,this.state,e),this.schedulePeriodicSync(e)},e.onerror=()=>{this.showError()},this.state.iframe=e,this.shadow.appendChild(e),this.cleanupListener=m(this.config,this.state,this.shadow,e,()=>this.scheduleRefresh(),()=>this.logoutAndClear(),()=>this.hideLoader(),(e,t)=>this.emit(e,t))}async mountWidget(){try{const t=window,r=["api_url","base_url","auth_api","socket_path_v2","socket_path","channel_api"];for(const e of r)t[e]||(t[e]=this.config.apiUrl);await(e=this.config.bundleUrl,new Promise((t,r)=>{const i=document.createElement("script");i.src=e,i.onload=()=>t(),i.onerror=()=>r(new Error(`Failed to load ${e}`)),document.head.appendChild(i)}));const i=p[this.config.widgetType],a=window[i];if(!(null==a?void 0:a.mount))throw new Error(`window.${i}.mount not found after loading bundle`);const s=document.createElement("div");if(s.style.cssText="position: relative; width: 100%; height: 100%;",this.appendChild(s),!this.shadow.querySelector("slot")){const e=document.createElement("slot");this.shadow.appendChild(e)}this.loaderEl&&(this.loaderEl.remove(),this.loaderEl=null),a.mount(s,{widgetType:this.config.widgetType,communityId:this.config.communityId,channelId:this.config.channelId,widgetId:this.config.communityId,apiUrl:this.config.apiUrl,token:this.state.accessToken,basePath:"/",isPreview:!1,isOpenWidget:!0,defaultColorSchema:"dark"===this.config.theme?"dark":"light"}),this.emit("rai-mounted")}catch(t){console.error("[rai-widget] Bundle mount failed:",t),this.showError()}var e}scheduleRefresh(){if(this.state.refreshTimer&&clearTimeout(this.state.refreshTimer),!this.state.accessTokenExpiry)return;const e=this.state.accessTokenExpiry-Date.now()-6e4,t=()=>{this.state.iframe&&f(this.config,this.state,this.state.iframe)},r=async()=>{if(this.config.authUrl){await c(this.config,this.state,()=>this.scheduleRefresh())&&t()}else await d(this.config,this.state,()=>this.scheduleRefresh(),t)};e>0?this.state.refreshTimer=setTimeout(r,e):r()}schedulePeriodicSync(e){this.state.syncTimer&&clearInterval(this.state.syncTimer),this.state.syncTimer=setInterval(()=>{this.state.accessToken&&f(this.config,this.state,e)},12e4)}async logoutAndClear(){await async function(e,t){if(t.accessToken)try{await fetch(`${e.apiUrl}/${e.widgetId}/auth/logout`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t.accessToken}`},body:JSON.stringify({refreshToken:t.refreshToken})})}catch{}u(t),s(e)}(this.config,this.state),this.shadow.querySelectorAll("iframe").forEach(e=>e.remove()),this.emit("rai-logout")}async reload(){u(this.state),this.shadow.querySelectorAll("iframe").forEach(e=>e.remove()),await this.init()}async logoutPublic(){await this.logoutAndClear()}isAuthenticated(){return this.state.isAuthenticated&&null!==this.state.accessToken}getTokenInfo(){return{hasAccessToken:!!this.state.accessToken,hasRefreshToken:!!this.state.refreshToken,accessTokenExpiry:this.state.accessTokenExpiry?new Date(this.state.accessTokenExpiry):null,refreshTokenExpiry:this.state.refreshTokenExpiry?new Date(this.state.refreshTokenExpiry):null,isAccessTokenValid:!!this.state.accessToken&&!n(this.state.accessTokenExpiry),isRefreshTokenValid:!!this.state.refreshToken&&!n(this.state.refreshTokenExpiry)}}}class k extends w{buildWidgetUrl(e){const t=new URL(e.widgetUrl);return t.searchParams.set("color",e.theme),t.searchParams.set("containerId",e.container),t.searchParams.set("connectType","simple"),t.searchParams.set("mode","private"),e.locale&&t.searchParams.set("locale",e.locale),t.toString()}}class b extends w{buildWidgetUrl(e){const t=e.widgetUrl.endsWith("channel-widget")?`${e.widgetUrl}/${e.widgetId}`:e.widgetUrl;if(!e.locale)return t;try{const r=new URL(t);return r.searchParams.set("locale",e.locale),r.toString()}catch{return`${t}?locale=${encodeURIComponent(e.locale)}`}}}class T extends w{buildWidgetUrl(e){const t=e.widgetUrl.endsWith("milestone-widget")?`${e.widgetUrl}/${e.widgetId}`:e.widgetUrl;if(!e.locale)return t;try{const r=new URL(t);return r.searchParams.set("locale",e.locale),r.toString()}catch{return`${t}?locale=${encodeURIComponent(e.locale)}`}}}class v extends w{buildWidgetUrl(e){const t=e.widgetUrl.endsWith("social-widget")?`${e.widgetUrl}/${e.widgetId}`:e.widgetUrl;if(!e.locale)return t;try{const r=new URL(t);return r.searchParams.set("locale",e.locale),r.toString()}catch{return`${t}?locale=${encodeURIComponent(e.locale)}`}}}class E extends w{buildWidgetUrl(e){const t=e.widgetUrl.endsWith("currency-overview-widget")?`${e.widgetUrl}/${e.widgetId}`:e.widgetUrl;if(!e.locale)return t;try{const r=new URL(t);return r.searchParams.set("locale",e.locale),r.toString()}catch{return`${t}?locale=${encodeURIComponent(e.locale)}`}}}console.log("[rai-widget] v1.1.0");const x=[["rai-store-widget",k],["rai-channel-widget",b],["rai-milestone-widget",T],["rai-social-widget",v],["rai-currency-widget",E],["rai-widget",class extends k{}]];for(const[R,C]of x)customElements.get(R)||customElements.define(R,C);const I={store:k,channel:b,milestone:T,social:v,"currency-view":E},U=x.map(([e])=>e).join(", ");function S(){var e,t;const r=((null==(e=document.currentScript)?void 0:e.hasAttribute("data-widget-id"))||(null==(t=document.currentScript)?void 0:t.hasAttribute("data-community-id"))?document.currentScript:null)||document.querySelector("script[data-widget-id], script[data-community-id]");if(!r)return;const i=r.getAttribute("data-community-id")||r.getAttribute("data-widget-id");if(!i)return;const a=r.getAttribute("data-container")||`returning-ai-widget-${i}`,s=document.getElementById(a);if(!s)return;"static"===getComputedStyle(s).position&&(s.style.position="relative");const n=r.getAttribute("data-widget-type")??"store",o=new(I[n]??k);Array.from(r.attributes).forEach(e=>{o.setAttribute(e.name,e.value)}),s.appendChild(o)}function A(){const e=(()=>{var e,t;const r=((null==(e=document.currentScript)?void 0:e.hasAttribute("data-widget-id"))||(null==(t=document.currentScript)?void 0:t.hasAttribute("data-community-id"))?document.currentScript:null)||document.querySelector("script[data-widget-id], script[data-community-id]");if(!r)return null;const i=r.getAttribute("data-container")||`returning-ai-widget-${r.getAttribute("data-community-id")||r.getAttribute("data-widget-id")}`;return document.getElementById(i)})(),t=null==e?void 0:e.querySelector(U);window.ReturningAIWidget={version:"1.1.0",reload:()=>(null==t?void 0:t.reload())??Promise.resolve(),logout:()=>(null==t?void 0:t.logoutPublic())??Promise.resolve(),isAuthenticated:()=>(null==t?void 0:t.isAuthenticated())??!1,getTokenInfo:()=>(null==t?void 0:t.getTokenInfo())??{}}}return"loading"===document.readyState?document.addEventListener("DOMContentLoaded",S):S(),"loading"===document.readyState?document.addEventListener("DOMContentLoaded",A):A(),e.ChannelWidget=b,e.CurrencyWidget=E,e.MilestoneWidget=T,e.SocialWidget=v,e.StoreWidget=k,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),e}({});
@@ -246,6 +246,18 @@ async function logout(config, state) {
246
246
  clearState(state);
247
247
  clearStorage(config);
248
248
  }
249
+ async function validateEmbedToken(config) {
250
+ try {
251
+ const res = await fetch(`${config.apiUrl}/v2/api/widget-access-keys/validate`, {
252
+ method: "POST",
253
+ headers: { "Content-Type": "application/json" },
254
+ body: JSON.stringify({ embedToken: config.embedToken })
255
+ });
256
+ return res.ok;
257
+ } catch {
258
+ return false;
259
+ }
260
+ }
249
261
  async function fetchErrorSettings(config, state) {
250
262
  const cached = loadErrorSettingsFromStorage(config);
251
263
  if (cached) {
@@ -392,9 +404,12 @@ function setupMessageListener(config, state, _shadow, iframe, onRefreshScheduled
392
404
  heightSetter.cancel();
393
405
  };
394
406
  }
395
- const widgetCSS = ":host{display:block;position:relative;width:100%;height:100%}.rai-loader{position:absolute;top:0;left:0;display:flex;align-items:center;justify-content:center;width:100%;height:100%;background:var(--rai-loader-bg, #ffffff);border-radius:8px;z-index:10;transition:opacity .3s ease-out}.rai-loader.fade-out{opacity:0;pointer-events:none}.rai-error{display:none;position:absolute;top:0;left:0;width:100%;height:100%;align-items:center;justify-content:center;flex-direction:column;gap:12px;background:var(--rai-error-bg, #1a1a1a);border-radius:8px;padding:24px;box-sizing:border-box;text-align:center;color:var(--rai-error-text, #9ca3af);font-family:system-ui,-apple-system,sans-serif;z-index:10}.rai-error.visible{display:flex}iframe{display:block;border:none;opacity:0;transition:opacity .3s ease-in}iframe.loaded{opacity:1}.loader{position:relative;width:75px;height:100px}.loader__bar{position:absolute;bottom:0;width:10px;height:50%;background:var(--rai-accent, #000000);transform-origin:center bottom;box-shadow:1px 1px #0003}.loader__bar:nth-child(1){left:0;transform:scaleY(.2);animation:barUp1 4s infinite}.loader__bar:nth-child(2){left:15px;transform:scaleY(.4);animation:barUp2 4s infinite}.loader__bar:nth-child(3){left:30px;transform:scaleY(.6);animation:barUp3 4s infinite}.loader__bar:nth-child(4){left:45px;transform:scaleY(.8);animation:barUp4 4s infinite}.loader__bar:nth-child(5){left:60px;transform:scale(1);animation:barUp5 4s infinite}.loader__ball{position:absolute;bottom:10px;left:0;width:10px;height:10px;background:var(--rai-accent, #000000);border-radius:50%;animation:ball 4s infinite}@keyframes ball{0%{transform:translate(0)}5%{transform:translate(8px,-14px)}10%{transform:translate(15px,-10px)}17%{transform:translate(23px,-24px)}20%{transform:translate(30px,-20px)}27%{transform:translate(38px,-34px)}30%{transform:translate(45px,-30px)}37%{transform:translate(53px,-44px)}40%{transform:translate(60px,-40px)}50%{transform:translate(60px)}57%{transform:translate(53px,-14px)}60%{transform:translate(45px,-10px)}67%{transform:translate(37px,-24px)}70%{transform:translate(30px,-20px)}77%{transform:translate(22px,-34px)}80%{transform:translate(15px,-30px)}87%{transform:translate(7px,-44px)}90%{transform:translateY(-40px)}to{transform:translate(0)}}@keyframes barUp1{0%{transform:scaleY(.2)}40%{transform:scaleY(.2)}50%{transform:scale(1)}90%{transform:scale(1)}to{transform:scaleY(.2)}}@keyframes barUp2{0%{transform:scaleY(.4)}40%{transform:scaleY(.4)}50%{transform:scaleY(.8)}90%{transform:scaleY(.8)}to{transform:scaleY(.4)}}@keyframes barUp3{0%{transform:scaleY(.6)}to{transform:scaleY(.6)}}@keyframes barUp4{0%{transform:scaleY(.8)}40%{transform:scaleY(.8)}50%{transform:scaleY(.4)}90%{transform:scaleY(.4)}to{transform:scaleY(.8)}}@keyframes barUp5{0%{transform:scale(1)}40%{transform:scale(1)}50%{transform:scaleY(.2)}90%{transform:scaleY(.2)}to{transform:scale(1)}}#loading-square{width:75px;aspect-ratio:1;display:flex;color:var(--rai-accent, #000000);background:linear-gradient(currentColor 0 0) right / 51% 100%,linear-gradient(currentColor 0 0) bottom / 100% 51%;background-repeat:no-repeat;animation:l16-0 2s infinite linear .25s}#loading-square>div{width:50%;height:50%;background:currentColor;animation:l16-1 .5s infinite linear}@keyframes l16-0{0%,12.49%{transform:rotate(0)}12.5%,37.49%{transform:rotate(90deg)}37.5%,62.49%{transform:rotate(180deg)}62.5%,87.49%{transform:rotate(270deg)}87.5%,to{transform:rotate(360deg)}}@keyframes l16-1{0%{transform:perspective(80px) rotate3d(-1,-1,0,0)}80%,to{transform:perspective(80px) rotate3d(-1,-1,0,-180deg)}}#loading-circle{width:75px;aspect-ratio:1;display:grid;grid:50%/50%;color:var(--rai-accent, #000000);border-radius:50%;--_g: no-repeat linear-gradient(currentColor 0 0);background:var(--_g),var(--_g),var(--_g);background-size:50.1% 50.1%;animation:l9-0 1.5s infinite steps(1) alternate,l9-0-0 3s infinite steps(1) alternate}#loading-circle>div{background:var(--rai-text4, #6b7280);border-top-left-radius:100px;transform:perspective(150px) rotateY(0) rotateX(0);transform-origin:bottom right;animation:l9-1 1.5s infinite linear alternate}@keyframes l9-0{0%{background-position:0 100%,100% 100%,100% 0}33%{background-position:100% 100%,100% 100%,100% 0}66%{background-position:100% 0,100% 0,100% 0}}@keyframes l9-0-0{0%{transform:scaleX(1) rotate(0)}50%{transform:scaleX(-1) rotate(-90deg)}}@keyframes l9-1{16.5%{transform:perspective(150px) rotateX(-90deg) rotateY(0) rotateX(0);filter:grayscale(.8)}33%{transform:perspective(150px) rotateX(-180deg) rotateY(0) rotateX(0)}66%{transform:perspective(150px) rotateX(-180deg) rotateY(-180deg) rotateX(0)}to{transform:perspective(150px) rotateX(-180deg) rotateY(-180deg) rotateX(-180deg);filter:grayscale(.8)}}.rai-retry-btn{padding:8px 20px;border:none;border-radius:6px;background:var(--rai-accent, #000000);color:var(--rai-error-bg, #ffffff);font-size:14px;font-family:system-ui,-apple-system,sans-serif;cursor:pointer}.rai-retry-btn:hover{opacity:.85}";
407
+ const widgetCSS = ":host{display:block;position:relative;width:100%;height:100%}.rai-loader{position:absolute;top:0;left:0;display:flex;align-items:center;justify-content:center;width:100%;height:100%;background:var(--rai-loader-bg, #ffffff);border-radius:8px;z-index:10;transition:opacity .3s ease-out}.rai-loader.fade-out{opacity:0;pointer-events:none}.rai-error{display:none;position:absolute;top:0;left:0;width:100%;height:100%;align-items:center;justify-content:center;flex-direction:column;gap:12px;background:var(--rai-error-bg, #1a1a1a);border-radius:8px;padding:24px;box-sizing:border-box;text-align:center;color:var(--rai-error-text, #9ca3af);font-family:system-ui,-apple-system,sans-serif;z-index:10}.rai-error.visible{display:flex}iframe{display:block;border:none;opacity:0;transition:opacity .3s ease-in}iframe.loaded{opacity:1}.rai-sdk-loader{position:relative;width:75px;height:100px}.rai-sdk-loader__bar{position:absolute;bottom:0;width:10px;height:50%;background:var(--rai-accent, #000000);transform-origin:center bottom;box-shadow:1px 1px #0003}.rai-sdk-loader__bar:nth-child(1){left:0;transform:scaleY(.2);animation:rai-sdk-barUp1 4s infinite}.rai-sdk-loader__bar:nth-child(2){left:15px;transform:scaleY(.4);animation:rai-sdk-barUp2 4s infinite}.rai-sdk-loader__bar:nth-child(3){left:30px;transform:scaleY(.6);animation:rai-sdk-barUp3 4s infinite}.rai-sdk-loader__bar:nth-child(4){left:45px;transform:scaleY(.8);animation:rai-sdk-barUp4 4s infinite}.rai-sdk-loader__bar:nth-child(5){left:60px;transform:scale(1);animation:rai-sdk-barUp5 4s infinite}.rai-sdk-loader__ball{position:absolute;bottom:10px;left:0;width:10px;height:10px;background:var(--rai-accent, #000000);border-radius:50%;animation:rai-sdk-ball 4s infinite}@keyframes rai-sdk-ball{0%{transform:translate(0)}5%{transform:translate(8px,-14px)}10%{transform:translate(15px,-10px)}17%{transform:translate(23px,-24px)}20%{transform:translate(30px,-20px)}27%{transform:translate(38px,-34px)}30%{transform:translate(45px,-30px)}37%{transform:translate(53px,-44px)}40%{transform:translate(60px,-40px)}50%{transform:translate(60px)}57%{transform:translate(53px,-14px)}60%{transform:translate(45px,-10px)}67%{transform:translate(37px,-24px)}70%{transform:translate(30px,-20px)}77%{transform:translate(22px,-34px)}80%{transform:translate(15px,-30px)}87%{transform:translate(7px,-44px)}90%{transform:translateY(-40px)}to{transform:translate(0)}}@keyframes rai-sdk-barUp1{0%{transform:scaleY(.2)}40%{transform:scaleY(.2)}50%{transform:scale(1)}90%{transform:scale(1)}to{transform:scaleY(.2)}}@keyframes rai-sdk-barUp2{0%{transform:scaleY(.4)}40%{transform:scaleY(.4)}50%{transform:scaleY(.8)}90%{transform:scaleY(.8)}to{transform:scaleY(.4)}}@keyframes rai-sdk-barUp3{0%{transform:scaleY(.6)}to{transform:scaleY(.6)}}@keyframes rai-sdk-barUp4{0%{transform:scaleY(.8)}40%{transform:scaleY(.8)}50%{transform:scaleY(.4)}90%{transform:scaleY(.4)}to{transform:scaleY(.8)}}@keyframes rai-sdk-barUp5{0%{transform:scale(1)}40%{transform:scale(1)}50%{transform:scaleY(.2)}90%{transform:scaleY(.2)}to{transform:scale(1)}}#loading-square{width:75px;aspect-ratio:1;display:flex;color:var(--rai-accent, #000000);background:linear-gradient(currentColor 0 0) right / 51% 100%,linear-gradient(currentColor 0 0) bottom / 100% 51%;background-repeat:no-repeat;animation:l16-0 2s infinite linear .25s}#loading-square>div{width:50%;height:50%;background:currentColor;animation:l16-1 .5s infinite linear}@keyframes l16-0{0%,12.49%{transform:rotate(0)}12.5%,37.49%{transform:rotate(90deg)}37.5%,62.49%{transform:rotate(180deg)}62.5%,87.49%{transform:rotate(270deg)}87.5%,to{transform:rotate(360deg)}}@keyframes l16-1{0%{transform:perspective(80px) rotate3d(-1,-1,0,0)}80%,to{transform:perspective(80px) rotate3d(-1,-1,0,-180deg)}}#loading-circle{width:75px;aspect-ratio:1;display:grid;grid:50%/50%;color:var(--rai-accent, #000000);border-radius:50%;--_g: no-repeat linear-gradient(currentColor 0 0);background:var(--_g),var(--_g),var(--_g);background-size:50.1% 50.1%;animation:l9-0 1.5s infinite steps(1) alternate,l9-0-0 3s infinite steps(1) alternate}#loading-circle>div{background:var(--rai-text4, #6b7280);border-top-left-radius:100px;transform:perspective(150px) rotateY(0) rotateX(0);transform-origin:bottom right;animation:l9-1 1.5s infinite linear alternate}@keyframes l9-0{0%{background-position:0 100%,100% 100%,100% 0}33%{background-position:100% 100%,100% 100%,100% 0}66%{background-position:100% 0,100% 0,100% 0}}@keyframes l9-0-0{0%{transform:scaleX(1) rotate(0)}50%{transform:scaleX(-1) rotate(-90deg)}}@keyframes l9-1{16.5%{transform:perspective(150px) rotateX(-90deg) rotateY(0) rotateX(0);filter:grayscale(.8)}33%{transform:perspective(150px) rotateX(-180deg) rotateY(0) rotateX(0)}66%{transform:perspective(150px) rotateX(-180deg) rotateY(-180deg) rotateX(0)}to{transform:perspective(150px) rotateX(-180deg) rotateY(-180deg) rotateX(-180deg);filter:grayscale(.8)}}.rai-retry-btn{padding:8px 20px;border:none;border-radius:6px;background:var(--rai-accent, #000000);color:var(--rai-error-bg, #ffffff);font-size:14px;font-family:system-ui,-apple-system,sans-serif;cursor:pointer}.rai-retry-btn:hover{opacity:.85}";
396
408
  const DEFINED_ATTRS = /* @__PURE__ */ new Set([
409
+ "community-id",
410
+ "channel-id",
397
411
  "widget-id",
412
+ // deprecated — 'community-id' is the canonical name
398
413
  "widget-type",
399
414
  "theme",
400
415
  "container",
@@ -412,18 +427,67 @@ const DEFINED_ATTRS = /* @__PURE__ */ new Set([
412
427
  "locale",
413
428
  "custom-data",
414
429
  "retry-label",
415
- "auth-url"
430
+ "auth-url",
431
+ "bundle-url",
432
+ "embed-token"
416
433
  ]);
434
+ const WIDGET_GLOBALS = {
435
+ store: "RaiStoreWidget",
436
+ channel: "RaiChannelWidget",
437
+ social: "RaiSocialWidget",
438
+ milestone: "RaiMilestoneWidget",
439
+ "currency-view": "RaiCurrencyWidget"
440
+ };
441
+ const TAG_TO_TYPE = {
442
+ "RAI-STORE-WIDGET": "store",
443
+ "RAI-CHANNEL-WIDGET": "channel",
444
+ "RAI-MILESTONE-WIDGET": "milestone",
445
+ "RAI-SOCIAL-WIDGET": "social",
446
+ "RAI-CURRENCY-WIDGET": "currency-view",
447
+ "RAI-WIDGET": "store"
448
+ // deprecated alias
449
+ };
450
+ function loadScript(url) {
451
+ return new Promise((resolve, reject) => {
452
+ const s = document.createElement("script");
453
+ s.src = url;
454
+ s.onload = () => resolve();
455
+ s.onerror = () => reject(new Error(`Failed to load ${url}`));
456
+ document.head.appendChild(s);
457
+ });
458
+ }
417
459
  function readConfig(el, existingId) {
418
460
  const get = (name) => el.getAttribute(name) ?? el.getAttribute(`data-${name}`) ?? "";
419
461
  const num = (name, def) => {
420
462
  const v = parseInt(get(name), 10);
421
463
  return Number.isFinite(v) && v >= 0 ? v : def;
422
464
  };
465
+ const usesExplicitIds = !!get("community-id");
466
+ const communityId = get("community-id") || get("widget-id") || existingId || "";
467
+ if (communityId && !/^[a-zA-Z0-9_\-=]{8,}$/.test(communityId)) {
468
+ throw new Error(`[rai-widget] Invalid community-id: "${communityId}"`);
469
+ }
470
+ const channelId = get("channel-id") || void 0;
471
+ if (channelId && !/^[a-zA-Z0-9_\-=]{8,}$/.test(channelId)) {
472
+ throw new Error(`[rai-widget] Invalid channel-id: "${channelId}"`);
473
+ }
474
+ const widgetType = get("widget-type") || TAG_TO_TYPE[el.tagName] || "store";
475
+ let widgetId;
476
+ if (usesExplicitIds) {
477
+ if (widgetType === "store") {
478
+ widgetId = communityId;
479
+ } else if (widgetType === "channel") {
480
+ widgetId = channelId ? btoa(channelId) : communityId;
481
+ } else {
482
+ widgetId = communityId ? btoa(communityId) : communityId;
483
+ }
484
+ } else {
485
+ widgetId = communityId;
486
+ }
423
487
  const rawWidgetUrl = get("widget-url") || "https://widget.returningai.com";
424
488
  let widgetUrl = rawWidgetUrl;
425
489
  if (widgetUrl.endsWith("store-widget")) {
426
- widgetUrl = `${widgetUrl}/${get("widget-id")}/open-widget`;
490
+ widgetUrl = `${widgetUrl}/${communityId}/open-widget`;
427
491
  }
428
492
  let widgetDomain;
429
493
  try {
@@ -436,11 +500,7 @@ function readConfig(el, existingId) {
436
500
  u.searchParams.set("color", get("theme") || "light");
437
501
  widgetUrl = u.toString();
438
502
  }
439
- const widgetId = get("widget-id") || existingId || "";
440
- if (widgetId && !/^[a-zA-Z0-9_\-=]{8,}$/.test(widgetId)) {
441
- throw new Error(`[rai-widget] Invalid widget-id format: "${widgetId}"`);
442
- }
443
- const container = get("container") || get("data-container") || `returning-ai-widget-${widgetId}`;
503
+ const container = get("container") || get("data-container") || `returning-ai-widget-${communityId}`;
444
504
  const userIdentifiers = {};
445
505
  Array.from(el.attributes).forEach((attr) => {
446
506
  const name = attr.name.toLowerCase();
@@ -460,8 +520,11 @@ function readConfig(el, existingId) {
460
520
  }
461
521
  })();
462
522
  return {
523
+ communityId,
524
+ channelId,
463
525
  widgetId,
464
- widgetType: get("widget-type") || "store",
526
+ // auth token — base64-encoded per widget type for auth URLs + iframe URL building
527
+ widgetType,
465
528
  theme: get("theme") || "light",
466
529
  container,
467
530
  width: get("width") || "100%",
@@ -484,8 +547,12 @@ function readConfig(el, existingId) {
484
547
  // #5
485
548
  customData,
486
549
  // #8
487
- authUrl: get("auth-url") || void 0
550
+ authUrl: get("auth-url") || void 0,
488
551
  // authenticated embed
552
+ bundleUrl: get("bundle-url") || void 0,
553
+ // bundle mode
554
+ embedToken: get("embed-token") || void 0
555
+ // required — issued by customer's server
489
556
  };
490
557
  }
491
558
  function createInitialState() {
@@ -505,7 +572,6 @@ function createInitialState() {
505
572
  };
506
573
  }
507
574
  class BaseWidget extends HTMLElement {
508
- // #3
509
575
  constructor() {
510
576
  super();
511
577
  __publicField(this, "shadow");
@@ -516,6 +582,8 @@ class BaseWidget extends HTMLElement {
516
582
  __publicField(this, "msgEl", null);
517
583
  __publicField(this, "cleanupListener");
518
584
  __publicField(this, "intersectionObserver");
585
+ // #3
586
+ __publicField(this, "themeObserver");
519
587
  this.shadow = this.attachShadow({ mode: "closed" });
520
588
  }
521
589
  connectedCallback() {
@@ -527,6 +595,8 @@ class BaseWidget extends HTMLElement {
527
595
  const accentColor = theme === "dark" ? "#ffffff" : "#000000";
528
596
  const text4Color = theme === "dark" ? "#9ca3af" : "#6b7280";
529
597
  const bgColor = theme === "dark" ? "#1a1a1a" : "#ffffff";
598
+ this.style.width = this.config.width;
599
+ this.style.height = this.config.height;
530
600
  this.style.setProperty("--rai-accent", accentColor);
531
601
  this.style.setProperty("--rai-text4", text4Color);
532
602
  this.style.setProperty("--rai-loader-bg", bgColor);
@@ -546,8 +616,9 @@ class BaseWidget extends HTMLElement {
546
616
  }
547
617
  }
548
618
  disconnectedCallback() {
549
- var _a;
619
+ var _a, _b;
550
620
  (_a = this.intersectionObserver) == null ? void 0 : _a.disconnect();
621
+ (_b = this.themeObserver) == null ? void 0 : _b.disconnect();
551
622
  if (this.cleanupListener) this.cleanupListener();
552
623
  clearState(this.state);
553
624
  }
@@ -583,14 +654,14 @@ class BaseWidget extends HTMLElement {
583
654
  }
584
655
  createDefaultLoader() {
585
656
  const loader = document.createElement("div");
586
- loader.className = "loader";
657
+ loader.className = "rai-sdk-loader";
587
658
  for (let i = 0; i < 5; i++) {
588
659
  const bar = document.createElement("div");
589
- bar.className = "loader__bar";
660
+ bar.className = "rai-sdk-loader__bar";
590
661
  loader.appendChild(bar);
591
662
  }
592
663
  const ball = document.createElement("div");
593
- ball.className = "loader__ball";
664
+ ball.className = "rai-sdk-loader__ball";
594
665
  loader.appendChild(ball);
595
666
  return loader;
596
667
  }
@@ -616,11 +687,25 @@ class BaseWidget extends HTMLElement {
616
687
  }
617
688
  // ── Initialization ─────────────────────────────────────────────────────
618
689
  async init() {
619
- if (!this.config.widgetId) {
690
+ if (!this.config.communityId) {
620
691
  this.showError();
621
692
  return;
622
693
  }
623
694
  await fetchErrorSettings(this.config, this.state);
695
+ if (this.config.embedToken) {
696
+ const valid = await validateEmbedToken(this.config);
697
+ if (!valid) {
698
+ this.showError();
699
+ return;
700
+ }
701
+ }
702
+ const launchWidget = () => {
703
+ if (this.config.bundleUrl) {
704
+ this.mountWidget();
705
+ } else {
706
+ this.createIframe();
707
+ }
708
+ };
624
709
  if (this.config.authUrl) {
625
710
  const authed2 = await authenticateViaProxy(
626
711
  this.config,
@@ -629,7 +714,7 @@ class BaseWidget extends HTMLElement {
629
714
  );
630
715
  if (authed2) {
631
716
  this.emit("rai-authenticated");
632
- this.createIframe();
717
+ launchWidget();
633
718
  } else {
634
719
  this.showError();
635
720
  }
@@ -648,7 +733,7 @@ class BaseWidget extends HTMLElement {
648
733
  }
649
734
  this.state.isAuthenticated = true;
650
735
  this.emit("rai-authenticated");
651
- this.createIframe();
736
+ launchWidget();
652
737
  return;
653
738
  }
654
739
  const authed = await authenticateServerless(
@@ -658,7 +743,7 @@ class BaseWidget extends HTMLElement {
658
743
  );
659
744
  if (authed) {
660
745
  this.emit("rai-authenticated");
661
- this.createIframe();
746
+ launchWidget();
662
747
  } else {
663
748
  this.showError();
664
749
  }
@@ -695,6 +780,58 @@ class BaseWidget extends HTMLElement {
695
780
  // #1
696
781
  );
697
782
  }
783
+ // ── Bundle mode (no-iframe) ──────────────────────────────────────────
784
+ async mountWidget() {
785
+ try {
786
+ const w = window;
787
+ const endpointVars = [
788
+ "api_url",
789
+ "base_url",
790
+ "auth_api",
791
+ "socket_path_v2",
792
+ "socket_path",
793
+ "channel_api"
794
+ ];
795
+ for (const v of endpointVars) {
796
+ if (!w[v]) w[v] = this.config.apiUrl;
797
+ }
798
+ await loadScript(this.config.bundleUrl);
799
+ const globalName = WIDGET_GLOBALS[this.config.widgetType];
800
+ const widgetGlobal = window[globalName];
801
+ if (!(widgetGlobal == null ? void 0 : widgetGlobal.mount)) {
802
+ throw new Error(`window.${globalName}.mount not found after loading bundle`);
803
+ }
804
+ const container = document.createElement("div");
805
+ container.style.cssText = "position: relative; width: 100%; height: 100%;";
806
+ this.appendChild(container);
807
+ if (!this.shadow.querySelector("slot")) {
808
+ const slot = document.createElement("slot");
809
+ this.shadow.appendChild(slot);
810
+ }
811
+ if (this.loaderEl) {
812
+ this.loaderEl.remove();
813
+ this.loaderEl = null;
814
+ }
815
+ widgetGlobal.mount(container, {
816
+ widgetType: this.config.widgetType,
817
+ communityId: this.config.communityId,
818
+ channelId: this.config.channelId,
819
+ // channel widget only; others receive undefined and ignore it
820
+ widgetId: this.config.communityId,
821
+ // deprecated alias kept for widget main.tsx compatibility
822
+ apiUrl: this.config.apiUrl,
823
+ token: this.state.accessToken,
824
+ basePath: "/",
825
+ isPreview: false,
826
+ isOpenWidget: true,
827
+ defaultColorSchema: this.config.theme === "dark" ? "dark" : "light"
828
+ });
829
+ this.emit("rai-mounted");
830
+ } catch (err) {
831
+ console.error("[rai-widget] Bundle mount failed:", err);
832
+ this.showError();
833
+ }
834
+ }
698
835
  // ── Token scheduling ──────────────────────────────────────────────────
699
836
  scheduleRefresh() {
700
837
  if (this.state.refreshTimer) clearTimeout(this.state.refreshTimer);
@@ -815,13 +952,17 @@ class CurrencyWidget extends BaseWidget {
815
952
  }
816
953
  }
817
954
  }
818
- console.log(`[rai-widget] v${"1.0.3"}`);
955
+ console.log(`[rai-widget] v${"1.1.0"}`);
956
+ class StoreWidgetCompat extends StoreWidget {
957
+ }
819
958
  const WIDGET_REGISTRY = [
820
- ["rai-widget", StoreWidget],
959
+ ["rai-store-widget", StoreWidget],
821
960
  ["rai-channel-widget", ChannelWidget],
822
961
  ["rai-milestone-widget", MilestoneWidget],
823
962
  ["rai-social-widget", SocialWidget],
824
- ["rai-currency-widget", CurrencyWidget]
963
+ ["rai-currency-widget", CurrencyWidget],
964
+ ["rai-widget", StoreWidgetCompat]
965
+ // deprecated alias — use <rai-store-widget> instead
825
966
  ];
826
967
  for (const [tag, cls] of WIDGET_REGISTRY) {
827
968
  if (!customElements.get(tag)) {
@@ -837,10 +978,10 @@ const WIDGET_CLASS_MAP = {
837
978
  };
838
979
  const ALL_WIDGET_SELECTOR = WIDGET_REGISTRY.map(([tag]) => tag).join(", ");
839
980
  function bootstrapFromScriptTag() {
840
- var _a;
841
- const script = (((_a = document.currentScript) == null ? void 0 : _a.hasAttribute("data-widget-id")) ? document.currentScript : null) || document.querySelector("script[data-widget-id]");
981
+ var _a, _b;
982
+ const script = (((_a = document.currentScript) == null ? void 0 : _a.hasAttribute("data-widget-id")) || ((_b = document.currentScript) == null ? void 0 : _b.hasAttribute("data-community-id")) ? document.currentScript : null) || document.querySelector("script[data-widget-id], script[data-community-id]");
842
983
  if (!script) return;
843
- const widgetId = script.getAttribute("data-widget-id");
984
+ const widgetId = script.getAttribute("data-community-id") || script.getAttribute("data-widget-id");
844
985
  if (!widgetId) return;
845
986
  const containerId = script.getAttribute("data-container") || `returning-ai-widget-${widgetId}`;
846
987
  const container = document.getElementById(containerId);
@@ -863,15 +1004,15 @@ if (document.readyState === "loading") {
863
1004
  }
864
1005
  function exposePublicApi() {
865
1006
  const container = (() => {
866
- var _a;
867
- const script = (((_a = document.currentScript) == null ? void 0 : _a.hasAttribute("data-widget-id")) ? document.currentScript : null) || document.querySelector("script[data-widget-id]");
1007
+ var _a, _b;
1008
+ const script = (((_a = document.currentScript) == null ? void 0 : _a.hasAttribute("data-widget-id")) || ((_b = document.currentScript) == null ? void 0 : _b.hasAttribute("data-community-id")) ? document.currentScript : null) || document.querySelector("script[data-widget-id], script[data-community-id]");
868
1009
  if (!script) return null;
869
- const id = script.getAttribute("data-container") || `returning-ai-widget-${script.getAttribute("data-widget-id")}`;
1010
+ const id = script.getAttribute("data-container") || `returning-ai-widget-${script.getAttribute("data-community-id") || script.getAttribute("data-widget-id")}`;
870
1011
  return document.getElementById(id);
871
1012
  })();
872
1013
  const widget = container == null ? void 0 : container.querySelector(ALL_WIDGET_SELECTOR);
873
1014
  window.ReturningAIWidget = {
874
- version: "1.0.3",
1015
+ version: "1.1.0",
875
1016
  reload: () => (widget == null ? void 0 : widget.reload()) ?? Promise.resolve(),
876
1017
  logout: () => (widget == null ? void 0 : widget.logoutPublic()) ?? Promise.resolve(),
877
1018
  isAuthenticated: () => (widget == null ? void 0 : widget.isAuthenticated()) ?? false,
@@ -9,6 +9,7 @@ export declare abstract class BaseWidget extends HTMLElement {
9
9
  private msgEl;
10
10
  private cleanupListener?;
11
11
  private intersectionObserver?;
12
+ private themeObserver?;
12
13
  constructor();
13
14
  protected abstract buildWidgetUrl(config: WidgetConfig): string;
14
15
  connectedCallback(): void;
@@ -20,6 +21,7 @@ export declare abstract class BaseWidget extends HTMLElement {
20
21
  private showError;
21
22
  private init;
22
23
  private createIframe;
24
+ private mountWidget;
23
25
  private scheduleRefresh;
24
26
  private schedulePeriodicSync;
25
27
  private logoutAndClear;
@@ -4,6 +4,7 @@ export declare function authenticateViaProxy(config: WidgetConfig, state: Widget
4
4
  export declare function refreshAccessToken(config: WidgetConfig, state: WidgetState, onRefreshScheduled?: () => void, sendToken?: () => void): Promise<boolean>;
5
5
  export declare function getValidToken(config: WidgetConfig, state: WidgetState, onRefreshScheduled?: () => void): Promise<string>;
6
6
  export declare function logout(config: WidgetConfig, state: WidgetState): Promise<void>;
7
+ export declare function validateEmbedToken(config: WidgetConfig): Promise<boolean>;
7
8
  export declare function fetchErrorSettings(config: WidgetConfig, state: WidgetState): Promise<typeof state.errorSettings>;
8
9
  declare function clearState(state: WidgetState): void;
9
10
  export { clearState };
@@ -1,4 +1,13 @@
1
1
  export interface WidgetConfig {
2
+ /** The community this widget belongs to. Maps to the `community-id` HTML attribute. */
3
+ communityId: string;
4
+ /** Channel widget only — the specific channel to display. Maps to the `channel-id` HTML attribute. */
5
+ channelId?: string;
6
+ /**
7
+ * @deprecated Alias for communityId — kept so subclass buildWidgetUrl() and widget
8
+ * main.tsx files that reference config.widgetId continue to work without changes.
9
+ * Set to the same value as communityId by readConfig().
10
+ */
2
11
  widgetId: string;
3
12
  widgetType: 'store' | 'channel' | 'milestone' | 'social' | 'currency-view';
4
13
  theme: 'light' | 'dark';
@@ -18,6 +27,8 @@ export interface WidgetConfig {
18
27
  locale?: string;
19
28
  customData?: Record<string, unknown>;
20
29
  authUrl?: string;
30
+ bundleUrl?: string;
31
+ embedToken?: string;
21
32
  }
22
33
  export interface WidgetState {
23
34
  accessToken: string | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@returningai/widget-sdk",
3
- "version": "1.0.3",
3
+ "version": "1.1.0",
4
4
  "description": "Shadow DOM isolated widget SDK for ReturningAI",
5
5
  "main": "dist/rai-widget.iife.js",
6
6
  "module": "dist/rai-widget.js",