@returningai/widget-sdk 1.0.2 → 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 +225 -89
- package/dist/rai-widget.iife.js +1 -1
- package/dist/rai-widget.js +435 -92
- package/dist/types/BaseWidget.d.ts +5 -0
- package/dist/types/core/auth.d.ts +2 -0
- package/dist/types/core/postmessage.d.ts +1 -1
- package/dist/types/types.d.ts +17 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
# ReturningAI Widget SDK
|
|
2
2
|
|
|
3
|
-
A
|
|
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
|
-
- [
|
|
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,31 +21,48 @@ 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
|
-
└── <
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
|
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
|
|
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, token refresh (
|
|
46
|
-
| Storage | `src/core/storage.ts` | `localStorage` helpers scoped to `{prefix}-{
|
|
47
|
-
| postMessage | `src/core/postmessage.ts` | Sends token to iframe;
|
|
48
|
-
| Styles | `src/styles/widget.css` | Loader animation, error state, iframe fade-in — all scoped inside Shadow DOM |
|
|
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 |
|
|
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` |
|
|
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` |
|
|
50
67
|
|
|
51
68
|
### Auth Flow
|
|
@@ -53,18 +70,43 @@ Customer page DOM
|
|
|
53
70
|
```
|
|
54
71
|
Page load
|
|
55
72
|
│
|
|
56
|
-
├─
|
|
57
|
-
│ Yes ──►
|
|
58
|
-
│
|
|
73
|
+
├─ embed-token present? (Access Key Embed)
|
|
74
|
+
│ Yes ──► validateEmbedToken()
|
|
75
|
+
│ ├─ valid ──► continue below
|
|
76
|
+
│ └─ invalid ──► showError() — stop
|
|
77
|
+
│ No ──► skip validation, continue below
|
|
59
78
|
│
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
79
|
+
├─ auth-url present? (Authenticated Embed)
|
|
80
|
+
│ Yes ──► authenticateViaProxy() ──► launch widget
|
|
81
|
+
│ No ──► loadFromStorage()
|
|
82
|
+
│ ├─ refresh token found ──► refreshAccessToken() ──► launch widget
|
|
83
|
+
│ └─ no token ──► authenticateServerless() ──► launch widget
|
|
84
|
+
│
|
|
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()
|
|
68
110
|
```
|
|
69
111
|
|
|
70
112
|
**Token storage strategy**
|
|
@@ -74,15 +116,15 @@ Token nearing expiry (1 min early)
|
|
|
74
116
|
| Access token | Memory only (`WidgetState.accessToken`) | ~5 min |
|
|
75
117
|
| Refresh token | `localStorage` | 7 days |
|
|
76
118
|
|
|
77
|
-
The access token is never written to `localStorage`. On every page load the refresh token is exchanged for a fresh access token before the
|
|
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.
|
|
78
120
|
|
|
79
|
-
### postMessage Protocol
|
|
121
|
+
### postMessage Protocol (iframe mode only)
|
|
80
122
|
|
|
81
123
|
Messages the SDK sends **to** the widget iframe:
|
|
82
124
|
|
|
83
125
|
| Type | Payload | When |
|
|
84
126
|
|------|---------|------|
|
|
85
|
-
| `RETURNINGAI_WIDGET_TOKEN` | `{ widgetId, token }` | After iframe load, on refresh, every 2 min |
|
|
127
|
+
| `RETURNINGAI_WIDGET_TOKEN` | `{ widgetId, token, customData? }` | After iframe load, on refresh, every 2 min |
|
|
86
128
|
|
|
87
129
|
Messages the SDK **receives** from the widget iframe:
|
|
88
130
|
|
|
@@ -98,29 +140,31 @@ All messages are origin-validated against `config.widgetDomain`.
|
|
|
98
140
|
|
|
99
141
|
---
|
|
100
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
|
+
|
|
101
157
|
## Shadow DOM Implementation
|
|
102
158
|
|
|
103
|
-
|
|
159
|
+
In iframe mode, the SDK uses a **closed** Shadow Root (`mode: 'closed'`), which means:
|
|
104
160
|
|
|
105
161
|
- Customer page CSS cannot reach any element inside the widget
|
|
106
162
|
- The widget's loader and error styles are fully encapsulated
|
|
107
163
|
- No class name collisions with customer frameworks (Tailwind, Bootstrap, etc.)
|
|
108
164
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
```typescript
|
|
112
|
-
// StoreWidget.ts — constructor
|
|
113
|
-
this.shadow = this.attachShadow({ mode: 'closed' })
|
|
114
|
-
|
|
115
|
-
// connectedCallback — CSS injected as a <style> tag inside the shadow root
|
|
116
|
-
const style = document.createElement('style')
|
|
117
|
-
style.textContent = widgetCSS // inlined at build time via Vite ?inline import
|
|
118
|
-
this.shadow.appendChild(style)
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
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.
|
|
122
166
|
|
|
123
|
-
### Theme variables
|
|
167
|
+
### Theme variables (iframe mode)
|
|
124
168
|
|
|
125
169
|
| Variable | Light | Dark |
|
|
126
170
|
|----------|-------|------|
|
|
@@ -141,7 +185,7 @@ None. The SDK ships as a single self-contained IIFE with no external runtime dep
|
|
|
141
185
|
|
|
142
186
|
| Package | Version | Purpose |
|
|
143
187
|
|---------|---------|---------|
|
|
144
|
-
| `vite` | ^5.1 | Bundles TypeScript + inlines CSS
|
|
188
|
+
| `vite` | ^5.1 | Bundles TypeScript + inlines CSS into a single IIFE |
|
|
145
189
|
| `typescript` | ^5.3 | Type checking and compilation |
|
|
146
190
|
| `terser` | ^5.46 | Minification for production builds |
|
|
147
191
|
|
|
@@ -152,6 +196,7 @@ None. The SDK ships as a single self-contained IIFE with no external runtime dep
|
|
|
152
196
|
| Custom Elements v1 | Chrome 67, Firefox 63, Safari 13 |
|
|
153
197
|
| Shadow DOM v1 | Chrome 53, Firefox 63, Safari 10 |
|
|
154
198
|
| `crypto.randomUUID()` | Chrome 92, Firefox 95, Safari 15.4 |
|
|
199
|
+
| `IntersectionObserver` | Chrome 58, Firefox 55, Safari 12.1 |
|
|
155
200
|
| `localStorage` | All modern browsers |
|
|
156
201
|
|
|
157
202
|
---
|
|
@@ -165,16 +210,16 @@ Zero changes required to existing embed HTML. Point `src` at the SDK and keep al
|
|
|
165
210
|
```html
|
|
166
211
|
<!-- 1. Container div (unchanged from current embed) -->
|
|
167
212
|
<div
|
|
168
|
-
id="returning-ai-widget-
|
|
213
|
+
id="returning-ai-widget-YOUR_COMMUNITY_ID"
|
|
169
214
|
style="width: 100%; height: 600px;"
|
|
170
215
|
></div>
|
|
171
216
|
|
|
172
217
|
<!-- 2. SDK script tag -->
|
|
173
218
|
<script
|
|
174
219
|
src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
|
|
175
|
-
data-
|
|
220
|
+
data-community-id="YOUR_COMMUNITY_ID"
|
|
176
221
|
data-widget-type="store"
|
|
177
|
-
data-container="returning-ai-widget-
|
|
222
|
+
data-container="returning-ai-widget-YOUR_COMMUNITY_ID"
|
|
178
223
|
data-theme="dark"
|
|
179
224
|
data-width="100%"
|
|
180
225
|
data-height="600px"
|
|
@@ -185,7 +230,53 @@ Zero changes required to existing embed HTML. Point `src` at the SDK and keep al
|
|
|
185
230
|
></script>
|
|
186
231
|
```
|
|
187
232
|
|
|
188
|
-
The SDK scans for the loader `<script>` tag, reads its `data-*` attributes, creates
|
|
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.
|
|
189
280
|
|
|
190
281
|
### Web Component
|
|
191
282
|
|
|
@@ -193,31 +284,31 @@ For customers using a JavaScript framework (React, Vue, Angular), import the SDK
|
|
|
193
284
|
|
|
194
285
|
| Tag | Widget type |
|
|
195
286
|
|-----|-------------|
|
|
196
|
-
| `<rai-widget>` | `store` |
|
|
287
|
+
| `<rai-store-widget>` | `store` |
|
|
197
288
|
| `<rai-channel-widget>` | `channel` |
|
|
198
289
|
| `<rai-milestone-widget>` | `milestone` |
|
|
199
290
|
| `<rai-social-widget>` | `social` |
|
|
200
291
|
| `<rai-currency-widget>` | `currency-view` |
|
|
292
|
+
| `<rai-widget>` | `store` *(deprecated alias — use `<rai-store-widget>`)* |
|
|
201
293
|
|
|
202
294
|
```html
|
|
203
295
|
<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"></script>
|
|
204
296
|
|
|
205
297
|
<!-- Store widget -->
|
|
206
|
-
<rai-widget
|
|
207
|
-
|
|
208
|
-
widget-type="store"
|
|
298
|
+
<rai-store-widget
|
|
299
|
+
community-id="YOUR_COMMUNITY_ID"
|
|
209
300
|
theme="dark"
|
|
210
301
|
width="100%"
|
|
211
302
|
height="600px"
|
|
212
303
|
api-url="YOUR_API_URL"
|
|
213
304
|
widget-url="YOUR_WIDGET_URL"
|
|
214
305
|
data-email="user@example.com"
|
|
215
|
-
></rai-widget>
|
|
306
|
+
></rai-store-widget>
|
|
216
307
|
|
|
217
|
-
<!-- Channel widget -->
|
|
308
|
+
<!-- Channel widget (requires channel-id) -->
|
|
218
309
|
<rai-channel-widget
|
|
219
|
-
|
|
220
|
-
|
|
310
|
+
community-id="YOUR_COMMUNITY_ID"
|
|
311
|
+
channel-id="YOUR_CHANNEL_ID"
|
|
221
312
|
theme="dark"
|
|
222
313
|
api-url="YOUR_API_URL"
|
|
223
314
|
widget-url="YOUR_WIDGET_URL"
|
|
@@ -233,9 +324,8 @@ import '@returningai/widget-sdk'
|
|
|
233
324
|
|
|
234
325
|
export function WidgetEmbed() {
|
|
235
326
|
return (
|
|
236
|
-
<rai-widget
|
|
237
|
-
|
|
238
|
-
widget-type="store"
|
|
327
|
+
<rai-store-widget
|
|
328
|
+
community-id="YOUR_COMMUNITY_ID"
|
|
239
329
|
theme="dark"
|
|
240
330
|
height="600px"
|
|
241
331
|
data-email={currentUser.email}
|
|
@@ -244,32 +334,94 @@ export function WidgetEmbed() {
|
|
|
244
334
|
}
|
|
245
335
|
```
|
|
246
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
|
+
|
|
247
367
|
### Configuration Attributes
|
|
248
368
|
|
|
249
369
|
All attributes can be provided with or without the `data-` prefix.
|
|
250
370
|
|
|
251
371
|
| Attribute | Required | Default | Description |
|
|
252
372
|
|-----------|----------|---------|-------------|
|
|
253
|
-
| `
|
|
373
|
+
| `community-id` | **Yes** | — | Your community ID — from Community Settings |
|
|
374
|
+
| `channel-id` | Channel only | — | Channel ID — required when `widget-type` is `channel` |
|
|
254
375
|
| `widget-type` | No | `store` | `store`, `channel`, `milestone`, `social`, `currency-view` |
|
|
255
376
|
| `theme` | No | `light` | `light` or `dark` |
|
|
256
377
|
| `container` | No | `returning-ai-widget-{id}` | ID of the container element |
|
|
257
|
-
| `width` | No | `100%` | CSS width
|
|
258
|
-
| `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) |
|
|
259
380
|
| `api-url` | No | — | Auth API base URL — from Community Settings |
|
|
260
|
-
| `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 |
|
|
261
385
|
| `auto-refresh` | No | `true` | Automatically refresh access token before expiry |
|
|
262
386
|
| `debug` | No | `false` | Enable verbose console logging |
|
|
263
|
-
| `
|
|
387
|
+
| `eager` | No | — | Boolean — skip `IntersectionObserver`, init immediately on mount |
|
|
388
|
+
| `locale` | No | — | BCP 47 tag appended as `?locale=` to the widget URL (e.g. `fr-FR`) |
|
|
389
|
+
| `max-retries` | No | `3` | Max auth retry attempts on network error or 5xx |
|
|
390
|
+
| `retry-delay` | No | `500` | Base backoff delay in ms; doubles each attempt |
|
|
391
|
+
| `height-debounce` | No | `100` | Debounce window in ms for `WIDGET_HEIGHT_UPDATE` |
|
|
392
|
+
| `storage-prefix` | No | `returning-ai-widget` | `localStorage` key prefix — set per tenant to avoid collisions |
|
|
393
|
+
| `retry-label` | No | `Retry` | Text for the retry button on the error screen |
|
|
394
|
+
| `custom-data` | No | — | JSON string forwarded as `customData` in the token postMessage |
|
|
395
|
+
| `data-email` | No | — | User identifier passed to auth (Public Embed only) |
|
|
264
396
|
| `data-*` | No | — | Any additional `data-*` attributes are forwarded as `userIdentifiers` to the auth API |
|
|
265
397
|
|
|
398
|
+
> **Note:** The legacy `widget-id` attribute is still supported as a deprecated alias for `community-id`.
|
|
399
|
+
|
|
400
|
+
### DOM Events
|
|
401
|
+
|
|
402
|
+
All events bubble and are `composed: true` (cross the Shadow DOM boundary).
|
|
403
|
+
|
|
404
|
+
| Event | `detail` | Fired when |
|
|
405
|
+
|-------|----------|-----------|
|
|
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) |
|
|
409
|
+
| `rai-error` | `{ message }` | Auth failed after all retries |
|
|
410
|
+
| `rai-logout` | `{}` | Widget logged out |
|
|
411
|
+
| `rai-height-change` | `{ height }` | iframe resized (after debounce) |
|
|
412
|
+
|
|
413
|
+
```js
|
|
414
|
+
document.querySelector('rai-channel-widget')
|
|
415
|
+
.addEventListener('rai-error', (e) => showToast(e.detail.message))
|
|
416
|
+
```
|
|
417
|
+
|
|
266
418
|
### Public API
|
|
267
419
|
|
|
268
420
|
After the SDK loads, `window.ReturningAIWidget` is available:
|
|
269
421
|
|
|
270
422
|
```javascript
|
|
271
423
|
// Check the loaded version
|
|
272
|
-
window.ReturningAIWidget.version // e.g. "1.0.
|
|
424
|
+
window.ReturningAIWidget.version // e.g. "1.0.3"
|
|
273
425
|
|
|
274
426
|
// Reload the widget (re-runs auth flow)
|
|
275
427
|
await window.ReturningAIWidget.reload()
|
|
@@ -335,27 +487,11 @@ npm run dev
|
|
|
335
487
|
|
|
336
488
|
### How the build works
|
|
337
489
|
|
|
338
|
-
1. **Entry**: `src/index.ts` — imports
|
|
490
|
+
1. **Entry**: `src/index.ts` — imports all widget subclasses, registers the custom elements, runs the bootstrap
|
|
339
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
|
|
340
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`
|
|
341
493
|
4. **Version injection**: `vite.config.ts` reads `version` from `package.json` and replaces the `__WIDGET_VERSION__` placeholder at build time
|
|
342
494
|
|
|
343
|
-
```
|
|
344
|
-
src/index.ts
|
|
345
|
-
├── src/StoreWidget.ts
|
|
346
|
-
│ └── src/BaseWidget.ts ← shared auth/iframe/Shadow DOM logic
|
|
347
|
-
│ ├── src/types.ts
|
|
348
|
-
│ ├── src/core/auth.ts
|
|
349
|
-
│ │ └── src/core/storage.ts
|
|
350
|
-
│ ├── src/core/storage.ts
|
|
351
|
-
│ ├── src/core/postmessage.ts
|
|
352
|
-
│ └── src/styles/widget.css [inlined as string]
|
|
353
|
-
├── src/ChannelWidget.ts └── src/BaseWidget.ts
|
|
354
|
-
├── src/MilestoneWidget.ts └── src/BaseWidget.ts
|
|
355
|
-
├── src/SocialWidget.ts └── src/BaseWidget.ts
|
|
356
|
-
└── src/CurrencyWidget.ts └── src/BaseWidget.ts
|
|
357
|
-
```
|
|
358
|
-
|
|
359
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).
|
|
360
496
|
|
|
361
497
|
---
|
|
@@ -366,12 +502,12 @@ To release a new version, bump `version` in `package.json`, rebuild, and upload
|
|
|
366
502
|
rai-widget-sdks/
|
|
367
503
|
├── src/
|
|
368
504
|
│ ├── types.ts # WidgetConfig, WidgetState, TokenData interfaces
|
|
369
|
-
│ ├── BaseWidget.ts # Abstract HTMLElement subclass — Shadow Root, auth, iframe
|
|
505
|
+
│ ├── BaseWidget.ts # Abstract HTMLElement subclass — Shadow Root, auth, iframe/bundle
|
|
370
506
|
│ ├── StoreWidget.ts # Extends BaseWidget; store micro-frontend URL builder
|
|
371
|
-
│ ├── ChannelWidget.ts # Extends BaseWidget; channel
|
|
372
|
-
│ ├── MilestoneWidget.ts # Extends BaseWidget; milestone
|
|
373
|
-
│ ├── SocialWidget.ts # Extends BaseWidget; social
|
|
374
|
-
│ ├── CurrencyWidget.ts # Extends BaseWidget; currency-view
|
|
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
|
|
375
511
|
│ ├── index.ts # Registers all 5 custom elements + IIFE bootstrap
|
|
376
512
|
│ ├── jsx.d.ts # React JSX IntrinsicElements + Vue GlobalComponents type shims
|
|
377
513
|
│ ├── vite-env.d.ts # Type declarations for ?inline imports + __WIDGET_VERSION__
|
package/dist/rai-widget.iife.js
CHANGED
|
@@ -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();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){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{return t.isAuthenticated=!1,!1}}async function c(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 h(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 h(t),s(e),l(e,t,r)}finally{t.isRefreshing=!1,t.refreshPromise=null}})(),t.refreshPromise)}async function d(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 h(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}},e.widgetDomain)}catch{}}function u(e,t,r,a,i,s,o){const d=async r=>{var d;if(r.origin!==e.widgetDomain)return;if(!r.data||"string"!=typeof r.data.type)return;const{type:h,containerId:f,widgetId:u,payload:g}=r.data;switch(h){case"RETURNINGAI_WIDGET_REQUEST_TOKEN":try{const r=await async function(e,t,r){if(t.accessToken&&!n(t.accessTokenExpiry))return t.accessToken;if(await c(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==(d=a.contentWindow)||d.postMessage({type:"RETURNINGAI_WIDGET_TOKEN",value:{token:r,widgetId:e.widgetId}},e.widgetDomain)}catch{}break;case"WIDGET_HEIGHT_UPDATE":{const e=Number(null==g?void 0:g.height);Number.isFinite(e)&&e>0&&(a.style.height=`${e}px`);break}case"WIDGET_READY":if(void 0!==u&&u!==e.widgetId)break;if(void 0!==f&&f!==e.container)break;a.classList.add("loaded"),o&&o();break;case"WIDGET_ERROR":o&&o();break;case"WIDGET_LOGOUT":s&&await s()}};return window.addEventListener("message",d),()=>window.removeEventListener("message",d)}const g=new Set(["widget-id","widget-type","theme","container","width","height","api-url","widget-url","auto-refresh","debug"]);class m 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,"cleanupListener"),this.shadow=this.attachShadow({mode:"closed"})}connectedCallback(){this.config=function(e,t){const r=t=>e.getAttribute(t)??e.getAttribute(`data-${t}`)??"";let a,i=r("widget-url")||"https://widget.returningai.com";i.endsWith("store-widget")&&(i=`${i}/${r("widget-id")}/open-widget`);try{a=new URL(i).origin}catch{a=i}if(i.includes("store-widget")){const e=new URL(i);e.searchParams.set("color",r("theme")||"light"),i=e.toString()}const s=r("widget-id")||t||"";if(s&&!/^[a-zA-Z0-9_\-=]{8,}$/.test(s))throw new Error(`[rai-widget] Invalid widget-id format: "${s}"`);const n=r("container")||r("data-container")||`returning-ai-widget-${s}`,o={};return Array.from(e.attributes).forEach(e=>{const t=e.name.toLowerCase();if(!t.startsWith("data-"))return;const r=t.slice(5);g.has(r)||(o[t]=e.value)}),{widgetId:s,widgetType:r("widget-type")||"store",theme:r("theme")||"light",container:n,width:r("width")||"100%",height:r("height")||"600px",apiUrl:r("api-url")||"https://sgtr-eks-widgets.genesiv.org",widgetUrl:i,widgetDomain:a,autoRefresh:"false"!==r("auto-refresh"),debug:"true"===r("debug"),storagePrefix:"returning-ai-widget",userIdentifiers:o}}(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)}}",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.init()}disconnectedCallback(){this.cleanupListener&&this.cleanupListener(),h(this.state)}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.errorEl.textContent="Authentication failed. Please try again later.",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;this.hideLoader(),this.errorEl&&((null==(e=this.state.errorSettings)?void 0:e.errorMessage)&&(this.errorEl.textContent=this.state.errorSettings.errorMessage),this.errorEl.classList.add("visible"))}async init(){if(!this.config.widgetId)return void this.showError();await d(this.config,this.state);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 c(this.config,this.state,()=>this.scheduleRefresh())?(this.state.isAuthenticated=!0,void this.createIframe()):void this.showError()}await l(this.config,this.state,()=>this.scheduleRefresh())?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=u(this.config,this.state,this.shadow,e,()=>this.scheduleRefresh(),()=>this.logoutAndClear(),()=>this.hideLoader())}scheduleRefresh(){if(this.state.refreshTimer&&clearTimeout(this.state.refreshTimer),!this.state.accessTokenExpiry)return;const e=this.state.accessTokenExpiry-Date.now()-6e4;e>0?this.state.refreshTimer=setTimeout(async()=>{await c(this.config,this.state,()=>this.scheduleRefresh(),()=>{this.state.iframe&&f(this.config,this.state,this.state.iframe)})},e):c(this.config,this.state,()=>this.scheduleRefresh(),()=>{this.state.iframe&&f(this.config,this.state,this.state.iframe)})}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{}h(t),s(e)}(this.config,this.state),this.shadow.querySelectorAll("iframe").forEach(e=>e.remove())}async reload(){h(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 p extends m{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"),t.toString()}}class w extends m{buildWidgetUrl(e){return e.widgetUrl.endsWith("channel-widget")?`${e.widgetUrl}/${e.widgetId}`:e.widgetUrl}}class y extends m{buildWidgetUrl(e){return e.widgetUrl.endsWith("milestone-widget")?`${e.widgetUrl}/${e.widgetId}`:e.widgetUrl}}class T extends m{buildWidgetUrl(e){return e.widgetUrl.endsWith("social-widget")?`${e.widgetUrl}/${e.widgetId}`:e.widgetUrl}}class k extends m{buildWidgetUrl(e){return e.widgetUrl.endsWith("currency-overview-widget")?`${e.widgetUrl}/${e.widgetId}`:e.widgetUrl}}console.log("[rai-widget] v1.0.2");const b=[["rai-widget",p],["rai-channel-widget",w],["rai-milestone-widget",y],["rai-social-widget",T],["rai-currency-widget",k]];for(const[S,U]of b)customElements.get(S)||customElements.define(S,U);const x={store:p,channel:w,milestone:y,social:T,"currency-view":k},E=b.map(([e])=>e).join(", ");function v(){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(x[s]??p);Array.from(t.attributes).forEach(e=>{n.setAttribute(e.name,e.value)}),i.appendChild(n)}function I(){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.2",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",v):v(),"loading"===document.readyState?document.addEventListener("DOMContentLoaded",I):I(),e.ChannelWidget=w,e.CurrencyWidget=k,e.MilestoneWidget=y,e.SocialWidget=T,e.StoreWidget=p,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}({});
|