@returningai/widget-sdk 1.0.3 → 1.1.1
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 +198 -91
- package/dist/rai-widget.iife.js +1 -1
- package/dist/rai-widget.js +174 -29
- package/dist/types/BaseWidget.d.ts +2 -0
- package/dist/types/core/auth.d.ts +1 -0
- package/dist/types/types.d.ts +12 -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,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
|
-
└── <
|
|
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 Shadow Root, auth lifecycle, iframe
|
|
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 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}-{
|
|
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
|
-
├─
|
|
57
|
-
│ Yes ──►
|
|
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() ──►
|
|
60
|
-
│ └─ no token ──► authenticateServerless() ──►
|
|
82
|
+
│ ├─ refresh token found ──► refreshAccessToken() ──► launch widget
|
|
83
|
+
│ └─ no token ──► authenticateServerless() ──► launch widget
|
|
61
84
|
│
|
|
62
|
-
└─
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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-
|
|
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-
|
|
220
|
+
data-community-id="YOUR_COMMUNITY_ID"
|
|
180
221
|
data-widget-type="store"
|
|
181
|
-
data-container="returning-ai-widget-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
224
|
-
|
|
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
|
-
|
|
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,55 @@ 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
|
-
| `
|
|
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
|
|
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
|
+
| `v2-api-url` | No | — | Base URL for the V2 API (embed token validation). Only needed when V2 API lives on a different host than `api-url` |
|
|
385
|
+
| `auth-url` | Auth Embed only | — | Your backend auth endpoint — enables Authenticated Embed mode |
|
|
265
386
|
| `auto-refresh` | No | `true` | Automatically refresh access token before expiry |
|
|
266
387
|
| `debug` | No | `false` | Enable verbose console logging |
|
|
267
388
|
| `eager` | No | — | Boolean — skip `IntersectionObserver`, init immediately on mount |
|
|
@@ -272,18 +393,20 @@ All attributes can be provided with or without the `data-` prefix.
|
|
|
272
393
|
| `storage-prefix` | No | `returning-ai-widget` | `localStorage` key prefix — set per tenant to avoid collisions |
|
|
273
394
|
| `retry-label` | No | `Retry` | Text for the retry button on the error screen |
|
|
274
395
|
| `custom-data` | No | — | JSON string forwarded as `customData` in the token postMessage |
|
|
275
|
-
| `
|
|
276
|
-
| `data-email` | No | — | User identifier passed to auth (public embed only) |
|
|
396
|
+
| `data-email` | No | — | User identifier passed to auth (Public Embed only) |
|
|
277
397
|
| `data-*` | No | — | Any additional `data-*` attributes are forwarded as `userIdentifiers` to the auth API |
|
|
278
398
|
|
|
399
|
+
> **Note:** The legacy `widget-id` attribute is still supported as a deprecated alias for `community-id`.
|
|
400
|
+
|
|
279
401
|
### DOM Events
|
|
280
402
|
|
|
281
403
|
All events bubble and are `composed: true` (cross the Shadow DOM boundary).
|
|
282
404
|
|
|
283
405
|
| Event | `detail` | Fired when |
|
|
284
406
|
|-------|----------|-----------|
|
|
285
|
-
| `rai-authenticated` | `{}` | Auth succeeded, before
|
|
286
|
-
| `rai-ready` | `{}` | `WIDGET_READY` received, loader hidden |
|
|
407
|
+
| `rai-authenticated` | `{}` | Auth succeeded, before widget mounts |
|
|
408
|
+
| `rai-ready` | `{}` | `WIDGET_READY` received, loader hidden (iframe mode) |
|
|
409
|
+
| `rai-mounted` | `{}` | Widget bundle mounted successfully (bundle mode) |
|
|
287
410
|
| `rai-error` | `{ message }` | Auth failed after all retries |
|
|
288
411
|
| `rai-logout` | `{}` | Widget logged out |
|
|
289
412
|
| `rai-height-change` | `{ height }` | iframe resized (after debounce) |
|
|
@@ -299,7 +422,7 @@ After the SDK loads, `window.ReturningAIWidget` is available:
|
|
|
299
422
|
|
|
300
423
|
```javascript
|
|
301
424
|
// Check the loaded version
|
|
302
|
-
window.ReturningAIWidget.version // e.g. "1.
|
|
425
|
+
window.ReturningAIWidget.version // e.g. "1.1.1"
|
|
303
426
|
|
|
304
427
|
// Reload the widget (re-runs auth flow)
|
|
305
428
|
await window.ReturningAIWidget.reload()
|
|
@@ -365,27 +488,11 @@ npm run dev
|
|
|
365
488
|
|
|
366
489
|
### How the build works
|
|
367
490
|
|
|
368
|
-
1. **Entry**: `src/index.ts` — imports
|
|
491
|
+
1. **Entry**: `src/index.ts` — imports all widget subclasses, registers the custom elements, runs the bootstrap
|
|
369
492
|
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
493
|
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
494
|
4. **Version injection**: `vite.config.ts` reads `version` from `package.json` and replaces the `__WIDGET_VERSION__` placeholder at build time
|
|
372
495
|
|
|
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
496
|
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
497
|
|
|
391
498
|
---
|
|
@@ -396,12 +503,12 @@ To release a new version, bump `version` in `package.json`, rebuild, and upload
|
|
|
396
503
|
rai-widget-sdks/
|
|
397
504
|
├── src/
|
|
398
505
|
│ ├── types.ts # WidgetConfig, WidgetState, TokenData interfaces
|
|
399
|
-
│ ├── BaseWidget.ts # Abstract HTMLElement subclass — Shadow Root, auth, iframe
|
|
506
|
+
│ ├── BaseWidget.ts # Abstract HTMLElement subclass — Shadow Root, auth, iframe/bundle
|
|
400
507
|
│ ├── StoreWidget.ts # Extends BaseWidget; store micro-frontend URL builder
|
|
401
|
-
│ ├── ChannelWidget.ts # Extends BaseWidget; channel
|
|
402
|
-
│ ├── MilestoneWidget.ts # Extends BaseWidget; milestone
|
|
403
|
-
│ ├── SocialWidget.ts # Extends BaseWidget; social
|
|
404
|
-
│ ├── CurrencyWidget.ts # Extends BaseWidget; currency-view
|
|
508
|
+
│ ├── ChannelWidget.ts # Extends BaseWidget; channel URL builder
|
|
509
|
+
│ ├── MilestoneWidget.ts # Extends BaseWidget; milestone URL builder
|
|
510
|
+
│ ├── SocialWidget.ts # Extends BaseWidget; social URL builder
|
|
511
|
+
│ ├── CurrencyWidget.ts # Extends BaseWidget; currency-view URL builder
|
|
405
512
|
│ ├── index.ts # Registers all 5 custom elements + IIFE bootstrap
|
|
406
513
|
│ ├── jsx.d.ts # React JSX IntrinsicElements + Vue GlobalComponents type shims
|
|
407
514
|
│ ├── 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(),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","v2-api-url"]),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,v2ApiUrl:r("v2-api-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}.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{const t=e.v2ApiUrl||e.apiUrl;return(await fetch(`${t}/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.1");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 A(){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 S(){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.1",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",A):A(),"loading"===document.readyState?document.addEventListener("DOMContentLoaded",S):S(),e.ChannelWidget=b,e.CurrencyWidget=E,e.MilestoneWidget=T,e.SocialWidget=v,e.StoreWidget=k,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),e}({});
|
package/dist/rai-widget.js
CHANGED
|
@@ -246,6 +246,19 @@ async function logout(config, state) {
|
|
|
246
246
|
clearState(state);
|
|
247
247
|
clearStorage(config);
|
|
248
248
|
}
|
|
249
|
+
async function validateEmbedToken(config) {
|
|
250
|
+
try {
|
|
251
|
+
const baseUrl = config.v2ApiUrl || config.apiUrl;
|
|
252
|
+
const res = await fetch(`${baseUrl}/v2/api/widget-access-keys/validate`, {
|
|
253
|
+
method: "POST",
|
|
254
|
+
headers: { "Content-Type": "application/json" },
|
|
255
|
+
body: JSON.stringify({ embedToken: config.embedToken })
|
|
256
|
+
});
|
|
257
|
+
return res.ok;
|
|
258
|
+
} catch {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
249
262
|
async function fetchErrorSettings(config, state) {
|
|
250
263
|
const cached = loadErrorSettingsFromStorage(config);
|
|
251
264
|
if (cached) {
|
|
@@ -392,9 +405,12 @@ function setupMessageListener(config, state, _shadow, iframe, onRefreshScheduled
|
|
|
392
405
|
heightSetter.cancel();
|
|
393
406
|
};
|
|
394
407
|
}
|
|
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}";
|
|
408
|
+
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
409
|
const DEFINED_ATTRS = /* @__PURE__ */ new Set([
|
|
410
|
+
"community-id",
|
|
411
|
+
"channel-id",
|
|
397
412
|
"widget-id",
|
|
413
|
+
// deprecated — 'community-id' is the canonical name
|
|
398
414
|
"widget-type",
|
|
399
415
|
"theme",
|
|
400
416
|
"container",
|
|
@@ -412,18 +428,68 @@ const DEFINED_ATTRS = /* @__PURE__ */ new Set([
|
|
|
412
428
|
"locale",
|
|
413
429
|
"custom-data",
|
|
414
430
|
"retry-label",
|
|
415
|
-
"auth-url"
|
|
431
|
+
"auth-url",
|
|
432
|
+
"bundle-url",
|
|
433
|
+
"embed-token",
|
|
434
|
+
"v2-api-url"
|
|
416
435
|
]);
|
|
436
|
+
const WIDGET_GLOBALS = {
|
|
437
|
+
store: "RaiStoreWidget",
|
|
438
|
+
channel: "RaiChannelWidget",
|
|
439
|
+
social: "RaiSocialWidget",
|
|
440
|
+
milestone: "RaiMilestoneWidget",
|
|
441
|
+
"currency-view": "RaiCurrencyWidget"
|
|
442
|
+
};
|
|
443
|
+
const TAG_TO_TYPE = {
|
|
444
|
+
"RAI-STORE-WIDGET": "store",
|
|
445
|
+
"RAI-CHANNEL-WIDGET": "channel",
|
|
446
|
+
"RAI-MILESTONE-WIDGET": "milestone",
|
|
447
|
+
"RAI-SOCIAL-WIDGET": "social",
|
|
448
|
+
"RAI-CURRENCY-WIDGET": "currency-view",
|
|
449
|
+
"RAI-WIDGET": "store"
|
|
450
|
+
// deprecated alias
|
|
451
|
+
};
|
|
452
|
+
function loadScript(url) {
|
|
453
|
+
return new Promise((resolve, reject) => {
|
|
454
|
+
const s = document.createElement("script");
|
|
455
|
+
s.src = url;
|
|
456
|
+
s.onload = () => resolve();
|
|
457
|
+
s.onerror = () => reject(new Error(`Failed to load ${url}`));
|
|
458
|
+
document.head.appendChild(s);
|
|
459
|
+
});
|
|
460
|
+
}
|
|
417
461
|
function readConfig(el, existingId) {
|
|
418
462
|
const get = (name) => el.getAttribute(name) ?? el.getAttribute(`data-${name}`) ?? "";
|
|
419
463
|
const num = (name, def) => {
|
|
420
464
|
const v = parseInt(get(name), 10);
|
|
421
465
|
return Number.isFinite(v) && v >= 0 ? v : def;
|
|
422
466
|
};
|
|
467
|
+
const usesExplicitIds = !!get("community-id");
|
|
468
|
+
const communityId = get("community-id") || get("widget-id") || existingId || "";
|
|
469
|
+
if (communityId && !/^[a-zA-Z0-9_\-=]{8,}$/.test(communityId)) {
|
|
470
|
+
throw new Error(`[rai-widget] Invalid community-id: "${communityId}"`);
|
|
471
|
+
}
|
|
472
|
+
const channelId = get("channel-id") || void 0;
|
|
473
|
+
if (channelId && !/^[a-zA-Z0-9_\-=]{8,}$/.test(channelId)) {
|
|
474
|
+
throw new Error(`[rai-widget] Invalid channel-id: "${channelId}"`);
|
|
475
|
+
}
|
|
476
|
+
const widgetType = get("widget-type") || TAG_TO_TYPE[el.tagName] || "store";
|
|
477
|
+
let widgetId;
|
|
478
|
+
if (usesExplicitIds) {
|
|
479
|
+
if (widgetType === "store") {
|
|
480
|
+
widgetId = communityId;
|
|
481
|
+
} else if (widgetType === "channel") {
|
|
482
|
+
widgetId = channelId ? btoa(channelId) : communityId;
|
|
483
|
+
} else {
|
|
484
|
+
widgetId = communityId ? btoa(communityId) : communityId;
|
|
485
|
+
}
|
|
486
|
+
} else {
|
|
487
|
+
widgetId = communityId;
|
|
488
|
+
}
|
|
423
489
|
const rawWidgetUrl = get("widget-url") || "https://widget.returningai.com";
|
|
424
490
|
let widgetUrl = rawWidgetUrl;
|
|
425
491
|
if (widgetUrl.endsWith("store-widget")) {
|
|
426
|
-
widgetUrl = `${widgetUrl}/${
|
|
492
|
+
widgetUrl = `${widgetUrl}/${communityId}/open-widget`;
|
|
427
493
|
}
|
|
428
494
|
let widgetDomain;
|
|
429
495
|
try {
|
|
@@ -436,11 +502,7 @@ function readConfig(el, existingId) {
|
|
|
436
502
|
u.searchParams.set("color", get("theme") || "light");
|
|
437
503
|
widgetUrl = u.toString();
|
|
438
504
|
}
|
|
439
|
-
const
|
|
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}`;
|
|
505
|
+
const container = get("container") || get("data-container") || `returning-ai-widget-${communityId}`;
|
|
444
506
|
const userIdentifiers = {};
|
|
445
507
|
Array.from(el.attributes).forEach((attr) => {
|
|
446
508
|
const name = attr.name.toLowerCase();
|
|
@@ -460,8 +522,11 @@ function readConfig(el, existingId) {
|
|
|
460
522
|
}
|
|
461
523
|
})();
|
|
462
524
|
return {
|
|
525
|
+
communityId,
|
|
526
|
+
channelId,
|
|
463
527
|
widgetId,
|
|
464
|
-
|
|
528
|
+
// auth token — base64-encoded per widget type for auth URLs + iframe URL building
|
|
529
|
+
widgetType,
|
|
465
530
|
theme: get("theme") || "light",
|
|
466
531
|
container,
|
|
467
532
|
width: get("width") || "100%",
|
|
@@ -484,8 +549,14 @@ function readConfig(el, existingId) {
|
|
|
484
549
|
// #5
|
|
485
550
|
customData,
|
|
486
551
|
// #8
|
|
487
|
-
authUrl: get("auth-url") || void 0
|
|
552
|
+
authUrl: get("auth-url") || void 0,
|
|
488
553
|
// authenticated embed
|
|
554
|
+
bundleUrl: get("bundle-url") || void 0,
|
|
555
|
+
// bundle mode
|
|
556
|
+
embedToken: get("embed-token") || void 0,
|
|
557
|
+
// required — issued by customer's server
|
|
558
|
+
v2ApiUrl: get("v2-api-url") || void 0
|
|
559
|
+
// separate V2 API host for embed-token validation
|
|
489
560
|
};
|
|
490
561
|
}
|
|
491
562
|
function createInitialState() {
|
|
@@ -505,7 +576,6 @@ function createInitialState() {
|
|
|
505
576
|
};
|
|
506
577
|
}
|
|
507
578
|
class BaseWidget extends HTMLElement {
|
|
508
|
-
// #3
|
|
509
579
|
constructor() {
|
|
510
580
|
super();
|
|
511
581
|
__publicField(this, "shadow");
|
|
@@ -516,6 +586,8 @@ class BaseWidget extends HTMLElement {
|
|
|
516
586
|
__publicField(this, "msgEl", null);
|
|
517
587
|
__publicField(this, "cleanupListener");
|
|
518
588
|
__publicField(this, "intersectionObserver");
|
|
589
|
+
// #3
|
|
590
|
+
__publicField(this, "themeObserver");
|
|
519
591
|
this.shadow = this.attachShadow({ mode: "closed" });
|
|
520
592
|
}
|
|
521
593
|
connectedCallback() {
|
|
@@ -527,6 +599,8 @@ class BaseWidget extends HTMLElement {
|
|
|
527
599
|
const accentColor = theme === "dark" ? "#ffffff" : "#000000";
|
|
528
600
|
const text4Color = theme === "dark" ? "#9ca3af" : "#6b7280";
|
|
529
601
|
const bgColor = theme === "dark" ? "#1a1a1a" : "#ffffff";
|
|
602
|
+
this.style.width = this.config.width;
|
|
603
|
+
this.style.height = this.config.height;
|
|
530
604
|
this.style.setProperty("--rai-accent", accentColor);
|
|
531
605
|
this.style.setProperty("--rai-text4", text4Color);
|
|
532
606
|
this.style.setProperty("--rai-loader-bg", bgColor);
|
|
@@ -546,8 +620,9 @@ class BaseWidget extends HTMLElement {
|
|
|
546
620
|
}
|
|
547
621
|
}
|
|
548
622
|
disconnectedCallback() {
|
|
549
|
-
var _a;
|
|
623
|
+
var _a, _b;
|
|
550
624
|
(_a = this.intersectionObserver) == null ? void 0 : _a.disconnect();
|
|
625
|
+
(_b = this.themeObserver) == null ? void 0 : _b.disconnect();
|
|
551
626
|
if (this.cleanupListener) this.cleanupListener();
|
|
552
627
|
clearState(this.state);
|
|
553
628
|
}
|
|
@@ -583,14 +658,14 @@ class BaseWidget extends HTMLElement {
|
|
|
583
658
|
}
|
|
584
659
|
createDefaultLoader() {
|
|
585
660
|
const loader = document.createElement("div");
|
|
586
|
-
loader.className = "loader";
|
|
661
|
+
loader.className = "rai-sdk-loader";
|
|
587
662
|
for (let i = 0; i < 5; i++) {
|
|
588
663
|
const bar = document.createElement("div");
|
|
589
|
-
bar.className = "loader__bar";
|
|
664
|
+
bar.className = "rai-sdk-loader__bar";
|
|
590
665
|
loader.appendChild(bar);
|
|
591
666
|
}
|
|
592
667
|
const ball = document.createElement("div");
|
|
593
|
-
ball.className = "loader__ball";
|
|
668
|
+
ball.className = "rai-sdk-loader__ball";
|
|
594
669
|
loader.appendChild(ball);
|
|
595
670
|
return loader;
|
|
596
671
|
}
|
|
@@ -616,11 +691,25 @@ class BaseWidget extends HTMLElement {
|
|
|
616
691
|
}
|
|
617
692
|
// ── Initialization ─────────────────────────────────────────────────────
|
|
618
693
|
async init() {
|
|
619
|
-
if (!this.config.
|
|
694
|
+
if (!this.config.communityId) {
|
|
620
695
|
this.showError();
|
|
621
696
|
return;
|
|
622
697
|
}
|
|
623
698
|
await fetchErrorSettings(this.config, this.state);
|
|
699
|
+
if (this.config.embedToken) {
|
|
700
|
+
const valid = await validateEmbedToken(this.config);
|
|
701
|
+
if (!valid) {
|
|
702
|
+
this.showError();
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
const launchWidget = () => {
|
|
707
|
+
if (this.config.bundleUrl) {
|
|
708
|
+
this.mountWidget();
|
|
709
|
+
} else {
|
|
710
|
+
this.createIframe();
|
|
711
|
+
}
|
|
712
|
+
};
|
|
624
713
|
if (this.config.authUrl) {
|
|
625
714
|
const authed2 = await authenticateViaProxy(
|
|
626
715
|
this.config,
|
|
@@ -629,7 +718,7 @@ class BaseWidget extends HTMLElement {
|
|
|
629
718
|
);
|
|
630
719
|
if (authed2) {
|
|
631
720
|
this.emit("rai-authenticated");
|
|
632
|
-
|
|
721
|
+
launchWidget();
|
|
633
722
|
} else {
|
|
634
723
|
this.showError();
|
|
635
724
|
}
|
|
@@ -648,7 +737,7 @@ class BaseWidget extends HTMLElement {
|
|
|
648
737
|
}
|
|
649
738
|
this.state.isAuthenticated = true;
|
|
650
739
|
this.emit("rai-authenticated");
|
|
651
|
-
|
|
740
|
+
launchWidget();
|
|
652
741
|
return;
|
|
653
742
|
}
|
|
654
743
|
const authed = await authenticateServerless(
|
|
@@ -658,7 +747,7 @@ class BaseWidget extends HTMLElement {
|
|
|
658
747
|
);
|
|
659
748
|
if (authed) {
|
|
660
749
|
this.emit("rai-authenticated");
|
|
661
|
-
|
|
750
|
+
launchWidget();
|
|
662
751
|
} else {
|
|
663
752
|
this.showError();
|
|
664
753
|
}
|
|
@@ -695,6 +784,58 @@ class BaseWidget extends HTMLElement {
|
|
|
695
784
|
// #1
|
|
696
785
|
);
|
|
697
786
|
}
|
|
787
|
+
// ── Bundle mode (no-iframe) ──────────────────────────────────────────
|
|
788
|
+
async mountWidget() {
|
|
789
|
+
try {
|
|
790
|
+
const w = window;
|
|
791
|
+
const endpointVars = [
|
|
792
|
+
"api_url",
|
|
793
|
+
"base_url",
|
|
794
|
+
"auth_api",
|
|
795
|
+
"socket_path_v2",
|
|
796
|
+
"socket_path",
|
|
797
|
+
"channel_api"
|
|
798
|
+
];
|
|
799
|
+
for (const v of endpointVars) {
|
|
800
|
+
if (!w[v]) w[v] = this.config.apiUrl;
|
|
801
|
+
}
|
|
802
|
+
await loadScript(this.config.bundleUrl);
|
|
803
|
+
const globalName = WIDGET_GLOBALS[this.config.widgetType];
|
|
804
|
+
const widgetGlobal = window[globalName];
|
|
805
|
+
if (!(widgetGlobal == null ? void 0 : widgetGlobal.mount)) {
|
|
806
|
+
throw new Error(`window.${globalName}.mount not found after loading bundle`);
|
|
807
|
+
}
|
|
808
|
+
const container = document.createElement("div");
|
|
809
|
+
container.style.cssText = "position: relative; width: 100%; height: 100%;";
|
|
810
|
+
this.appendChild(container);
|
|
811
|
+
if (!this.shadow.querySelector("slot")) {
|
|
812
|
+
const slot = document.createElement("slot");
|
|
813
|
+
this.shadow.appendChild(slot);
|
|
814
|
+
}
|
|
815
|
+
if (this.loaderEl) {
|
|
816
|
+
this.loaderEl.remove();
|
|
817
|
+
this.loaderEl = null;
|
|
818
|
+
}
|
|
819
|
+
widgetGlobal.mount(container, {
|
|
820
|
+
widgetType: this.config.widgetType,
|
|
821
|
+
communityId: this.config.communityId,
|
|
822
|
+
channelId: this.config.channelId,
|
|
823
|
+
// channel widget only; others receive undefined and ignore it
|
|
824
|
+
widgetId: this.config.communityId,
|
|
825
|
+
// deprecated alias kept for widget main.tsx compatibility
|
|
826
|
+
apiUrl: this.config.apiUrl,
|
|
827
|
+
token: this.state.accessToken,
|
|
828
|
+
basePath: "/",
|
|
829
|
+
isPreview: false,
|
|
830
|
+
isOpenWidget: true,
|
|
831
|
+
defaultColorSchema: this.config.theme === "dark" ? "dark" : "light"
|
|
832
|
+
});
|
|
833
|
+
this.emit("rai-mounted");
|
|
834
|
+
} catch (err) {
|
|
835
|
+
console.error("[rai-widget] Bundle mount failed:", err);
|
|
836
|
+
this.showError();
|
|
837
|
+
}
|
|
838
|
+
}
|
|
698
839
|
// ── Token scheduling ──────────────────────────────────────────────────
|
|
699
840
|
scheduleRefresh() {
|
|
700
841
|
if (this.state.refreshTimer) clearTimeout(this.state.refreshTimer);
|
|
@@ -815,13 +956,17 @@ class CurrencyWidget extends BaseWidget {
|
|
|
815
956
|
}
|
|
816
957
|
}
|
|
817
958
|
}
|
|
818
|
-
console.log(`[rai-widget] v${"1.
|
|
959
|
+
console.log(`[rai-widget] v${"1.1.1"}`);
|
|
960
|
+
class StoreWidgetCompat extends StoreWidget {
|
|
961
|
+
}
|
|
819
962
|
const WIDGET_REGISTRY = [
|
|
820
|
-
["rai-widget", StoreWidget],
|
|
963
|
+
["rai-store-widget", StoreWidget],
|
|
821
964
|
["rai-channel-widget", ChannelWidget],
|
|
822
965
|
["rai-milestone-widget", MilestoneWidget],
|
|
823
966
|
["rai-social-widget", SocialWidget],
|
|
824
|
-
["rai-currency-widget", CurrencyWidget]
|
|
967
|
+
["rai-currency-widget", CurrencyWidget],
|
|
968
|
+
["rai-widget", StoreWidgetCompat]
|
|
969
|
+
// deprecated alias — use <rai-store-widget> instead
|
|
825
970
|
];
|
|
826
971
|
for (const [tag, cls] of WIDGET_REGISTRY) {
|
|
827
972
|
if (!customElements.get(tag)) {
|
|
@@ -837,10 +982,10 @@ const WIDGET_CLASS_MAP = {
|
|
|
837
982
|
};
|
|
838
983
|
const ALL_WIDGET_SELECTOR = WIDGET_REGISTRY.map(([tag]) => tag).join(", ");
|
|
839
984
|
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]");
|
|
985
|
+
var _a, _b;
|
|
986
|
+
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
987
|
if (!script) return;
|
|
843
|
-
const widgetId = script.getAttribute("data-widget-id");
|
|
988
|
+
const widgetId = script.getAttribute("data-community-id") || script.getAttribute("data-widget-id");
|
|
844
989
|
if (!widgetId) return;
|
|
845
990
|
const containerId = script.getAttribute("data-container") || `returning-ai-widget-${widgetId}`;
|
|
846
991
|
const container = document.getElementById(containerId);
|
|
@@ -863,15 +1008,15 @@ if (document.readyState === "loading") {
|
|
|
863
1008
|
}
|
|
864
1009
|
function exposePublicApi() {
|
|
865
1010
|
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]");
|
|
1011
|
+
var _a, _b;
|
|
1012
|
+
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
1013
|
if (!script) return null;
|
|
869
|
-
const id = script.getAttribute("data-container") || `returning-ai-widget-${script.getAttribute("data-widget-id")}`;
|
|
1014
|
+
const id = script.getAttribute("data-container") || `returning-ai-widget-${script.getAttribute("data-community-id") || script.getAttribute("data-widget-id")}`;
|
|
870
1015
|
return document.getElementById(id);
|
|
871
1016
|
})();
|
|
872
1017
|
const widget = container == null ? void 0 : container.querySelector(ALL_WIDGET_SELECTOR);
|
|
873
1018
|
window.ReturningAIWidget = {
|
|
874
|
-
version: "1.
|
|
1019
|
+
version: "1.1.1",
|
|
875
1020
|
reload: () => (widget == null ? void 0 : widget.reload()) ?? Promise.resolve(),
|
|
876
1021
|
logout: () => (widget == null ? void 0 : widget.logoutPublic()) ?? Promise.resolve(),
|
|
877
1022
|
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 };
|
package/dist/types/types.d.ts
CHANGED
|
@@ -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,9 @@ export interface WidgetConfig {
|
|
|
18
27
|
locale?: string;
|
|
19
28
|
customData?: Record<string, unknown>;
|
|
20
29
|
authUrl?: string;
|
|
30
|
+
bundleUrl?: string;
|
|
31
|
+
embedToken?: string;
|
|
32
|
+
v2ApiUrl?: string;
|
|
21
33
|
}
|
|
22
34
|
export interface WidgetState {
|
|
23
35
|
accessToken: string | null;
|