@returningai/widget-sdk 1.2.15 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,527 +1,505 @@
1
- # @returningai/widget-sdk
2
-
3
- Embed ReturningAI widgets on any website — React, Vue, Angular, or plain `<script>` tag. No external dependencies.
4
-
5
- ---
6
-
7
- ## Quick Start
8
-
9
- ### 1. Get your credentials
10
-
11
- Log in to your **ReturningAI dashboard** and go to **Community Settings > Integrations > SDK Access**. You will find:
12
-
13
- - **Widget ID** — identifies your widget
14
- - **API URL** — the base URL for widget authentication
15
- - **Widget URL** — the URL that loads the widget content
16
-
17
- ### 2. Add the SDK to your page
18
-
19
- ```html
20
- <div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
21
- <script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
22
- data-widget-id="YOUR_WIDGET_ID"
23
- data-widget-type="store"
24
- data-theme="dark"
25
- data-email="user@example.com"
26
- api-url="YOUR_API_URL"
27
- widget-url="YOUR_WIDGET_URL"
28
- ></script>
29
- ```
30
-
31
- That's it. The widget will authenticate and render automatically.
32
-
33
- ---
34
-
35
- ## Authentication
36
-
37
- ### Public Embed (no backend needed)
38
-
39
- The simplest way to embed a widget. The SDK handles everything — no server-side code required.
40
-
41
- ```html
42
- <div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
43
- <script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
44
- data-widget-id="YOUR_WIDGET_ID"
45
- data-widget-type="store"
46
- data-theme="dark"
47
- data-email="user@example.com"
48
- api-url="YOUR_API_URL"
49
- widget-url="YOUR_WIDGET_URL"
50
- ></script>
51
- ```
52
-
53
- The `data-email` attribute identifies the user. The SDK authenticates directly with the ReturningAI backend on the client side.
54
-
55
- **When to use:** Demos, internal tools, or any scenario where you don't have a backend server.
56
-
57
- ### Access Key Embed (recommended for production)
58
-
59
- Your access credentials stay on your server. Your server exchanges them for a **short-lived embed token (15 min)** and injects only that token into the page. No secret ever reaches the browser.
60
-
61
- **Step 1 — Create an access key pair** (one-time, from your dashboard):
62
-
63
- Go to **Community Settings > Integrations > SDK Access** and click **Create SDK Key**. Enter a name and optional allowed origins.
64
-
65
- The dashboard will display your `accessId` and `accessKey`. The `accessKey` is shown **once only** — save it securely.
66
-
67
- Store both values in your server environment variables.
68
-
69
- **Step 2 — Exchange credentials for an embed token on every page load** (Node.js example):
70
-
71
- ```js
72
- // Runs on YOUR SERVER — never in the browser
73
- const response = await fetch('https://YOUR_API_URL/v2/api/widget-access-keys/token', {
74
- method: 'POST',
75
- headers: { 'Content-Type': 'application/json' },
76
- body: JSON.stringify({
77
- accessId: process.env.RAI_ACCESS_ID,
78
- accessKey: process.env.RAI_ACCESS_KEY,
79
- }),
80
- })
81
- const { data } = await response.json()
82
- // data.embedToken expires in 15 minutes
83
- ```
84
-
85
- **Step 3 — Embed** (only the short-lived token appears in HTML):
86
-
87
- ```html
88
- <div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
89
- <script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
90
- data-widget-id="YOUR_WIDGET_ID"
91
- data-widget-type="store"
92
- data-theme="dark"
93
- data-email="user@example.com"
94
- data-embed-token="eyJ..."
95
- api-url="YOUR_API_URL"
96
- widget-url="YOUR_WIDGET_URL"
97
- ></script>
98
- ```
99
-
100
- The SDK validates the token on widget load. If the token is expired or invalid, the widget shows an error screen. Tokens expire after 15 minutes — regenerate on each page render.
101
-
102
- ---
103
-
104
- ## Install
105
-
106
- ### CDN (no install needed)
107
-
108
- ```html
109
- <script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"></script>
110
- ```
111
-
112
- ### npm / yarn / pnpm
113
-
114
- ```bash
115
- npm install @returningai/widget-sdk
116
- ```
117
-
118
- ---
119
-
120
- ## Widget Types
121
-
122
- Set the `data-widget-type` attribute to choose which widget to display. The Widget ID and Widget URL for each type are available in your dashboard.
123
-
124
- | `data-widget-type` | Description |
125
- |---------------------|-------------|
126
- | `store` | Store / rewards widget |
127
- | `channel` | Channel widget |
128
- | `milestone` | Milestone widget |
129
- | `social` | Social widget |
130
- | `currency-view` | Currency widget |
131
- | `referral-conditions` | Referral widget |
132
- | `custom` | Custom widget |
133
-
134
- ### Store widget
135
-
136
- ```html
137
- <div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
138
- <script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
139
- data-widget-id="YOUR_WIDGET_ID"
140
- data-widget-type="store"
141
- data-theme="dark"
142
- data-email="user@example.com"
143
- api-url="YOUR_API_URL"
144
- widget-url="YOUR_WIDGET_URL"
145
- ></script>
146
- ```
147
-
148
- ### Channel widget
149
-
150
- ```html
151
- <div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
152
- <script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
153
- data-widget-id="YOUR_WIDGET_ID"
154
- data-widget-type="channel"
155
- data-theme="dark"
156
- data-email="user@example.com"
157
- api-url="YOUR_API_URL"
158
- widget-url="YOUR_WIDGET_URL"
159
- ></script>
160
- ```
161
-
162
- > **Note:** `YOUR_WIDGET_ID` for channel widgets is the base64-encoded channel ID — find it under **Community Settings > Channels** in your dashboard. Alternatively, use the [custom element approach](#framework-usage-custom-elements) with separate `community-id` and `channel-id` attributes.
163
-
164
- ### Social widget
165
-
166
- ```html
167
- <div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
168
- <script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
169
- data-widget-id="YOUR_WIDGET_ID"
170
- data-widget-type="social"
171
- data-theme="dark"
172
- data-email="user@example.com"
173
- api-url="YOUR_API_URL"
174
- widget-url="YOUR_WIDGET_URL"
175
- ></script>
176
- ```
177
-
178
- ### Milestone widget
179
-
180
- ```html
181
- <div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
182
- <script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
183
- data-widget-id="YOUR_WIDGET_ID"
184
- data-widget-type="milestone"
185
- data-theme="dark"
186
- data-email="user@example.com"
187
- api-url="YOUR_API_URL"
188
- widget-url="YOUR_WIDGET_URL"
189
- ></script>
190
- ```
191
-
192
- ### Custom widget
193
-
194
- ```html
195
- <div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
196
- <script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
197
- data-widget-id="YOUR_WIDGET_ID"
198
- data-widget-type="custom"
199
- data-theme="dark"
200
- data-email="user@example.com"
201
- api-url="YOUR_API_URL"
202
- widget-url="YOUR_WIDGET_URL"
203
- ></script>
204
- ```
205
-
206
- ---
207
-
208
- ## Attributes
209
-
210
- There are two ways to identify your widget:
211
-
212
- 1. **Script-tag embed:** Use `data-widget-id` (a pre-encoded ID from your dashboard) on the `<script>` tag
213
- 2. **Custom element embed:** Use `community-id` (and `channel-id` for channel widgets) on the custom element tag
214
-
215
- All attributes can be provided with or without the `data-` prefix — both work.
216
-
217
- ### Script-tag attributes
218
-
219
- | Attribute | Required | Default | Description |
220
- |-----------|----------|---------|-------------|
221
- | `data-widget-id` | **Yes** | — | Your widget ID (from dashboard) |
222
- | `data-widget-type` | **Yes** | `store` | Widget type (see table above) |
223
- | `api-url` | **Yes**\* | — | Auth API base URL (from dashboard) |
224
- | `widget-url` | **Yes**\* | — | Widget URL (from dashboard). \*Not required when using `data-bundle-url` |
225
- | `data-bundle-url` | No | — | URL to the widget IIFE bundle — triggers [bundle mode](#bundle-mode) (non-iframe) |
226
- | `data-domain-key` | Bundle mode | — | Environment key: `SGTR`, `STG`, or `PROD`. Required for bundle mode on external sites |
227
- | `data-theme` | No | `light` | `light` or `dark` |
228
- | `data-email` | Recommended | — | User's email for identification |
229
- | `data-embed-token` | Access Key only | — | Short-lived token from your server |
230
- | `data-locale` | No | — | BCP 47 locale tag (e.g. `fr-FR`) |
231
- | `data-eager` | No | — | Load immediately instead of waiting until visible |
232
- | `data-auto-refresh` | No | `true` | Auto-refresh access token before expiry |
233
- | `data-debug` | No | `false` | Verbose console logging |
234
- | `data-custom-data` | No | — | JSON object forwarded to the widget |
235
-
236
- ### Custom element attributes
237
-
238
- | Attribute | Required | Default | Description |
239
- |-----------|----------|---------|-------------|
240
- | `community-id` | **Yes**\* | — | Your community ID (raw ObjectId from dashboard). \*Custom widgets use `widget-id` instead |
241
- | `channel-id` | **Channel only** | — | Channel ID (raw ObjectId) — required for `<rai-channel-widget>` |
242
- | `api-url` | **Yes** | — | Auth API base URL (from dashboard) |
243
- | `widget-url` | **Yes**\* | — | Widget URL (from dashboard). \*Not required when using `bundle-url` |
244
- | `bundle-url` | No | — | URL to the widget IIFE bundle — triggers [bundle mode](#bundle-mode) (non-iframe) |
245
- | `domain-key` | Bundle mode | — | Environment key: `SGTR`, `STG`, or `PROD`. Required for bundle mode on external sites |
246
- | `theme` | No | `light` | `light` or `dark` |
247
- | `data-email` | Recommended | — | User's email for identification |
248
- | `embed-token` | Access Key only | — | Short-lived token from your server |
249
- | `width` | No | `100%` | CSS width |
250
- | `height` | No | `600px` | CSS height |
251
- | `locale` | No | — | BCP 47 locale tag (e.g. `fr-FR`) |
252
- | `eager` | No | — | Load immediately instead of waiting until visible |
253
- | `auto-refresh` | No | `true` | Auto-refresh access token before expiry |
254
- | `debug` | No | `false` | Verbose console logging |
255
- | `custom-data` | No | — | JSON object forwarded to the widget |
256
-
257
- > **`widget-id` vs `community-id`:** The script-tag method uses `data-widget-id` which is a pre-encoded identifier (base64). The custom element method uses raw ObjectIds — `community-id` for most widget types, plus `channel-id` for channel widgets. The SDK handles the encoding internally.
258
-
259
- ### Container `<div>` (script-tag method only)
260
-
261
- The SDK looks for a `<div>` with `id="returning-ai-widget-{YOUR_WIDGET_ID}"`. Size the div to control the widget dimensions:
262
-
263
- ```html
264
- <div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
265
- ```
266
-
267
- Custom elements don't need a container div — they size themselves using `width` and `height` attributes (defaults: `100%` and `600px`).
268
-
269
- ### User Identifier Attributes
270
-
271
- Pass any `data-*` attribute to identify the current user. Which identifiers are available is configured per community in your dashboard.
272
-
273
- ```html
274
- <script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
275
- data-widget-id="YOUR_WIDGET_ID"
276
- data-widget-type="store"
277
- data-email="user@example.com"
278
- data-user-id="12345"
279
- data-account-id="abc"
280
- api-url="YOUR_API_URL"
281
- widget-url="YOUR_WIDGET_URL"
282
- ></script>
283
- ```
284
-
285
- Any `data-*` attribute not reserved by the SDK is forwarded to the auth API as a user identifier.
286
-
287
- ---
288
-
289
- ## Bundle Mode
290
-
291
- 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.
292
-
293
- `widget-url` is not needed in bundle mode. `domain-key` is required so the widget resolves the correct environment URLs.
294
-
295
- ```html
296
- <!-- Script-tag approach -->
297
- <div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:100vh"></div>
298
- <script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
299
- data-widget-id="YOUR_WIDGET_ID"
300
- data-widget-type="store"
301
- data-theme="dark"
302
- data-email="user@example.com"
303
- data-bundle-url="YOUR_BUNDLE_URL"
304
- data-domain-key="YOUR_DOMAIN_KEY"
305
- api-url="YOUR_API_URL"
306
- data-eager
307
- ></script>
308
-
309
- <!-- Custom element approach -->
310
- <rai-store-widget
311
- community-id="YOUR_COMMUNITY_ID"
312
- api-url="YOUR_API_URL"
313
- bundle-url="YOUR_BUNDLE_URL"
314
- domain-key="YOUR_DOMAIN_KEY"
315
- data-email="user@example.com"
316
- theme="dark"
317
- width="100%"
318
- height="100vh"
319
- eager
320
- ></rai-store-widget>
321
- ```
322
-
323
- Each widget type has its own IIFE bundle and global name:
324
-
325
- | Widget type | Bundle global | Custom element tag |
326
- |-------------|--------------|-------------------|
327
- | `store` | `RaiStoreWidget` | `<rai-store-widget>` |
328
- | `channel` | `RaiChannelWidget` | `<rai-channel-widget>` |
329
- | `social` | `RaiSocialWidget` | `<rai-social-widget>` |
330
- | `milestone` | `RaiMilestoneWidget` | `<rai-milestone-widget>` |
331
- | `currency-view` | `RaiCurrencyWidget` | `<rai-currency-widget>` |
332
- | `referral-conditions` | `RaiReferralWidget` | `<rai-referral-widget>` |
333
- | `custom` | `RaiCustomWidget` | `<rai-custom-widget>` |
334
-
335
- The bundle must export a `mount(container, config)` function on its global (e.g. `window.RaiStoreWidget.mount`).
336
-
337
- ---
338
-
339
- ## Framework Usage (Custom Elements)
340
-
341
- When using a frontend framework (React, Vue, Angular), you can use custom HTML element tags instead of the script-tag method. Custom elements use raw IDs (`community-id`, `channel-id`) instead of the pre-encoded `data-widget-id`.
342
-
343
- ### Plain HTML
344
-
345
- ```html
346
- <script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"></script>
347
-
348
- <!-- Store widget -->
349
- <rai-store-widget
350
- community-id="YOUR_COMMUNITY_ID"
351
- theme="dark"
352
- data-email="user@example.com"
353
- api-url="YOUR_API_URL"
354
- widget-url="YOUR_WIDGET_URL"
355
- ></rai-store-widget>
356
-
357
- <!-- Channel widget — requires both community-id AND channel-id -->
358
- <rai-channel-widget
359
- community-id="YOUR_COMMUNITY_ID"
360
- channel-id="YOUR_CHANNEL_ID"
361
- theme="dark"
362
- data-email="user@example.com"
363
- api-url="YOUR_API_URL"
364
- widget-url="YOUR_WIDGET_URL"
365
- ></rai-channel-widget>
366
- ```
367
-
368
- ### React (TypeScript)
369
-
370
- Types are included — no manual JSX declarations needed.
371
-
372
- ```tsx
373
- import '@returningai/widget-sdk'
374
-
375
- export function StoreEmbed({ email }: { email: string }) {
376
- return (
377
- <rai-store-widget
378
- community-id="YOUR_COMMUNITY_ID"
379
- theme="dark"
380
- data-email={email}
381
- api-url="YOUR_API_URL"
382
- widget-url="YOUR_WIDGET_URL"
383
- />
384
- )
385
- }
386
-
387
- export function ChannelEmbed({ email }: { email: string }) {
388
- return (
389
- <rai-channel-widget
390
- community-id="YOUR_COMMUNITY_ID"
391
- channel-id="YOUR_CHANNEL_ID"
392
- theme="dark"
393
- data-email={email}
394
- api-url="YOUR_API_URL"
395
- widget-url="YOUR_WIDGET_URL"
396
- />
397
- )
398
- }
399
- ```
400
-
401
- ### Vue 3
402
-
403
- ```vue
404
- <script setup>
405
- import '@returningai/widget-sdk'
406
- </script>
407
-
408
- <template>
409
- <!-- Store widget -->
410
- <rai-store-widget
411
- community-id="YOUR_COMMUNITY_ID"
412
- theme="dark"
413
- :data-email="user.email"
414
- api-url="YOUR_API_URL"
415
- widget-url="YOUR_WIDGET_URL"
416
- />
417
-
418
- <!-- Channel widget -->
419
- <rai-channel-widget
420
- community-id="YOUR_COMMUNITY_ID"
421
- channel-id="YOUR_CHANNEL_ID"
422
- theme="dark"
423
- :data-email="user.email"
424
- api-url="YOUR_API_URL"
425
- widget-url="YOUR_WIDGET_URL"
426
- />
427
- </template>
428
- ```
429
-
430
- ### Angular
431
-
432
- ```ts
433
- // app.module.ts
434
- import '@returningai/widget-sdk'
435
- import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
436
-
437
- @NgModule({ schemas: [CUSTOM_ELEMENTS_SCHEMA] })
438
- ```
439
-
440
- ```html
441
- <!-- Store widget -->
442
- <rai-store-widget
443
- community-id="YOUR_COMMUNITY_ID"
444
- theme="dark"
445
- [attr.data-email]="user.email"
446
- api-url="YOUR_API_URL"
447
- widget-url="YOUR_WIDGET_URL"
448
- ></rai-store-widget>
449
-
450
- <!-- Channel widget -->
451
- <rai-channel-widget
452
- community-id="YOUR_COMMUNITY_ID"
453
- channel-id="YOUR_CHANNEL_ID"
454
- theme="dark"
455
- [attr.data-email]="user.email"
456
- api-url="YOUR_API_URL"
457
- widget-url="YOUR_WIDGET_URL"
458
- ></rai-channel-widget>
459
- ```
460
-
461
- ### Available custom element tags
462
-
463
- | Tag | Required attributes | Widget type |
464
- |-----|-------------------|-------------|
465
- | `<rai-store-widget>` | `community-id` | Store |
466
- | `<rai-channel-widget>` | `community-id` + `channel-id` | Channel |
467
- | `<rai-milestone-widget>` | `community-id` | Milestone |
468
- | `<rai-social-widget>` | `community-id` | Social |
469
- | `<rai-currency-widget>` | `community-id` | Currency |
470
- | `<rai-referral-widget>` | `community-id` | Referral |
471
- | `<rai-custom-widget>` | `widget-id` (not `community-id`) | Custom |
472
-
473
- ---
474
-
475
- ## DOM Events
476
-
477
- Listen for widget lifecycle events:
478
-
479
- | Event | `detail` | Fired when |
480
- |-------|----------|-----------|
481
- | `rai-authenticated` | `{}` | Auth succeeded, before widget mounts |
482
- | `rai-ready` | `{}` | `WIDGET_READY` received, loader hidden (iframe mode) |
483
- | `rai-mounted` | `{}` | Widget bundle mounted successfully (bundle mode) |
484
- | `rai-error` | `{ message }` | Auth failed after all retries |
485
- | `rai-logout` | `{}` | Widget logged out |
486
- | `rai-height-change` | `{ height }` | iframe resized (after debounce) |
487
-
488
- ```js
489
- // Script-tag embed
490
- const widget = document.querySelector('[data-widget-id]')
491
-
492
- // Custom element embed
493
- const widget = document.querySelector('rai-store-widget')
494
-
495
- widget.addEventListener('rai-authenticated', () => { /* auth succeeded */ })
496
- widget.addEventListener('rai-ready', () => { /* widget loaded and visible (iframe mode) */ })
497
- widget.addEventListener('rai-mounted', () => { /* widget mounted (bundle mode) */ })
498
- widget.addEventListener('rai-error', (e) => console.error(e.detail.message))
499
- widget.addEventListener('rai-logout', () => { /* user logged out */ })
500
- widget.addEventListener('rai-height-change', (e) => console.log(e.detail.height))
501
- ```
502
-
503
- ---
504
-
505
- ## Public API
506
-
507
- After the SDK loads, `window.ReturningAIWidget` is available:
508
-
509
- ```js
510
- window.ReturningAIWidget.version // current SDK version
511
- await window.ReturningAIWidget.reload() // re-runs auth flow and reloads widget
512
- await window.ReturningAIWidget.logout() // clears tokens and removes widget
513
- window.ReturningAIWidget.isAuthenticated() // boolean
514
- window.ReturningAIWidget.getTokenInfo() // token metadata (no token values exposed)
515
- ```
516
-
517
- ---
518
-
519
- ## Browser Support
520
-
521
- Requires Custom Elements v1 + Shadow DOM v1.
522
-
523
- | Browser | Minimum version |
524
- |---------|----------------|
525
- | Chrome | 67 |
526
- | Firefox | 63 |
527
- | Safari | 13 |
1
+ # @returningai/widget-sdk
2
+
3
+ Embed ReturningAI widgets on any website — React, Vue, Angular, or plain `<script>` tag. No external dependencies.
4
+
5
+ ---
6
+
7
+ ## Quick Start
8
+
9
+ Your access credentials stay on your server. Your server exchanges them for a **short-lived embed token (15 min)** and injects only that token into the page. No secret ever reaches the browser.
10
+
11
+ **Step 1 — Create an access key pair** (one-time, from your dashboard):
12
+
13
+ Go to **Community Settings > Integrations > SDK Access** and click **Create SDK Key**. Enter a name and optional allowed origins (required in production; localhost is auto-allowed in dev).
14
+
15
+ The dashboard will display your `accessId` and `accessKey`. The `accessKey` is shown **once only** — save it securely.
16
+
17
+ Store both values in your server environment variables.
18
+
19
+ > **Rotating or revoking keys:** The same dashboard page lists existing keys and lets you revoke or replace them. Revocation takes effect immediately for token issuance, and forces active sessions to re-auth on their next page load.
20
+
21
+ **Step 2 — Exchange credentials for an embed token on every page load** (Node.js example):
22
+
23
+ ```js
24
+ // Runs on YOUR SERVER — never in the browser
25
+ const response = await fetch('https://YOUR_API_URL/v2/api/widget-access-keys/token', {
26
+ method: 'POST',
27
+ headers: { 'Content-Type': 'application/json' },
28
+ body: JSON.stringify({
29
+ accessId: process.env.RAI_ACCESS_ID,
30
+ accessKey: process.env.RAI_ACCESS_KEY,
31
+ // userIdentifiers keys MUST be `data-*` prefixed — they match the
32
+ // `dataAttribute` value of each field configured in your widget's
33
+ // User Identifier Fields (e.g. the dashboard stores "data-email",
34
+ // "data-user-id", not bare "email"/"userId").
35
+ userIdentifiers: {
36
+ 'data-user-id': [YourUserObject].id,
37
+ 'data-email': [YourUserObject].email,
38
+ // ...any other fields configured for this widget
39
+ },
40
+ }),
41
+ })
42
+ const { data } = await response.json()
43
+ // data.embedToken expires in 15 minutes and carries signed identity claims
44
+ ```
45
+
46
+ **Step 3 — Embed** (only the short-lived token appears in HTML — no identity attributes):
47
+
48
+ ```html
49
+ <div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
50
+ <script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
51
+ data-widget-id="YOUR_WIDGET_ID"
52
+ data-widget-type="store"
53
+ data-theme="dark"
54
+ data-embed-token="eyJ..."
55
+ api-url="YOUR_API_URL"
56
+ widget-url="YOUR_WIDGET_URL"
57
+ ></script>
58
+ ```
59
+
60
+ The server validates the token and extracts identity from its signed claims. If the token is expired, invalid, or its identifiers don't satisfy the widget's configured User Identifier Fields, the widget shows an error screen. Tokens expire after 15 minutes — regenerate on each page render.
61
+
62
+ ---
63
+
64
+ ## Install
65
+
66
+ ### CDN (no install needed)
67
+
68
+ ```html
69
+ <script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"></script>
70
+ ```
71
+
72
+ ### npm / yarn / pnpm
73
+
74
+ ```bash
75
+ npm install @returningai/widget-sdk
76
+ ```
77
+
78
+ ---
79
+
80
+ ## Widget Types
81
+
82
+ Set the `data-widget-type` attribute to choose which widget to display. The Widget ID and Widget URL for each type are available in your dashboard.
83
+
84
+ | `data-widget-type` | Description |
85
+ |---------------------|-------------|
86
+ | `store` | Store / rewards widget |
87
+ | `channel` | Channel widget |
88
+ | `milestone` | Milestone widget |
89
+ | `social` | Social widget |
90
+ | `currency-view` | Currency widget |
91
+ | `referral-conditions` | Referral widget |
92
+ | `custom` | Custom widget |
93
+
94
+ All examples below use **Access Key Embed** (recommended) — identity is signed into `data-embed-token` server-side per the [Quick Start](#quick-start). Swap `data-embed-token="eyJ..."` for `data-*` identifier attributes if you want [Public Embed](#user-identifier-attributes) instead.
95
+
96
+ ### Store widget
97
+
98
+ ```html
99
+ <div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
100
+ <script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
101
+ data-widget-id="YOUR_WIDGET_ID"
102
+ data-widget-type="store"
103
+ data-theme="dark"
104
+ data-embed-token="eyJ..."
105
+ api-url="YOUR_API_URL"
106
+ widget-url="YOUR_WIDGET_URL"
107
+ ></script>
108
+ ```
109
+
110
+ ### Channel widget
111
+
112
+ ```html
113
+ <div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
114
+ <script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
115
+ data-widget-id="YOUR_WIDGET_ID"
116
+ data-widget-type="channel"
117
+ data-theme="dark"
118
+ data-embed-token="eyJ..."
119
+ api-url="YOUR_API_URL"
120
+ widget-url="YOUR_WIDGET_URL"
121
+ ></script>
122
+ ```
123
+
124
+ > **Note:** `YOUR_WIDGET_ID` for channel widgets is the base64-encoded channel ID — find it under **Community Settings > Channels** in your dashboard. Alternatively, use the [custom element approach](#framework-usage-custom-elements) with separate `community-id` and `channel-id` attributes.
125
+
126
+ ### Social widget
127
+
128
+ ```html
129
+ <div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
130
+ <script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
131
+ data-widget-id="YOUR_WIDGET_ID"
132
+ data-widget-type="social"
133
+ data-theme="dark"
134
+ data-embed-token="eyJ..."
135
+ api-url="YOUR_API_URL"
136
+ widget-url="YOUR_WIDGET_URL"
137
+ ></script>
138
+ ```
139
+
140
+ ### Milestone widget
141
+
142
+ ```html
143
+ <div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
144
+ <script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
145
+ data-widget-id="YOUR_WIDGET_ID"
146
+ data-widget-type="milestone"
147
+ data-theme="dark"
148
+ data-embed-token="eyJ..."
149
+ api-url="YOUR_API_URL"
150
+ widget-url="YOUR_WIDGET_URL"
151
+ ></script>
152
+ ```
153
+
154
+ ### Custom widget
155
+
156
+ ```html
157
+ <div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
158
+ <script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
159
+ data-widget-id="YOUR_WIDGET_ID"
160
+ data-widget-type="custom"
161
+ data-theme="dark"
162
+ data-embed-token="eyJ..."
163
+ api-url="YOUR_API_URL"
164
+ widget-url="YOUR_WIDGET_URL"
165
+ ></script>
166
+ ```
167
+
168
+ > **Custom widgets:** `YOUR_WIDGET_ID` is the base64-encoded `_id` of the CustomWidget record (not the community ID). The custom element variant `<rai-custom-widget widget-id="...">` uses this same base64 value — it's the one widget type where raw ObjectIds are not accepted.
169
+
170
+ ---
171
+
172
+ ## Attributes
173
+
174
+ There are two ways to identify your widget:
175
+
176
+ 1. **Script-tag embed:** Use `data-widget-id` (a pre-encoded ID from your dashboard) on the `<script>` tag
177
+ 2. **Custom element embed:** Use `community-id` (and `channel-id` for channel widgets) on the custom element tag
178
+
179
+ All attributes can be provided with or without the `data-` prefix — both work.
180
+
181
+ ### Script-tag attributes
182
+
183
+ | Attribute | Required | Default | Description |
184
+ |-----------|----------|---------|-------------|
185
+ | `data-widget-id` | **Yes** | — | Your widget ID (from dashboard) |
186
+ | `data-widget-type` | **Yes** | `store` | Widget type (see table above) |
187
+ | `api-url` | **Yes**\* | — | Auth API base URL (from dashboard) |
188
+ | `widget-url` | **Yes**\* | — | Widget URL (from dashboard). \*Not required when using `data-bundle-url` |
189
+ | `data-bundle-url` | No | — | URL to the widget IIFE bundle — triggers [bundle mode](#bundle-mode) (non-iframe) |
190
+ | `data-domain-key` | Bundle mode | — | Environment key: `SGTR`, `STG`, or `PROD`. Required for bundle mode on external sites |
191
+ | `data-theme` | No | `light` | `light` or `dark` |
192
+ | `data-email` | Public Embed only | — | User's email for identification. **Access Key Embed:** omit — identity is signed into `data-embed-token` |
193
+ | `data-embed-token` | Access Key Embed only | — | Short-lived JWT from your server. Carries signed user identifiers — no `data-*` identifier attributes needed |
194
+ | `data-locale` | No | — | BCP 47 locale tag (e.g. `fr-FR`) |
195
+ | `data-eager` | No | — | Load immediately instead of waiting until visible (presence-based boolean) |
196
+ | `data-auto-refresh` | No | `true` | Auto-refresh access token before expiry |
197
+ | `data-debug` | No | `false` | Verbose console logging |
198
+ | `data-custom-data` | No | — | JSON object forwarded to the widget |
199
+ | `data-retry-label` | No | `Retry` | Label text for the retry button on the error screen |
200
+ | `data-v2-api-url` | Reserved | — | Reserved for future deployments where V2 API endpoints live on a different host than `api-url`. Currently unused — safe to omit |
201
+
202
+ ### Custom element attributes
203
+
204
+ | Attribute | Required | Default | Description |
205
+ |-----------|----------|---------|-------------|
206
+ | `community-id` | **Yes**\* | — | Your community ID (raw ObjectId from dashboard). \*Custom widgets use `widget-id` instead |
207
+ | `channel-id` | **Channel only** | — | Channel ID (raw ObjectId) — required for `<rai-channel-widget>` |
208
+ | `api-url` | **Yes** | — | Auth API base URL (from dashboard) |
209
+ | `widget-url` | **Yes**\* | — | Widget URL (from dashboard). \*Not required when using `bundle-url` |
210
+ | `bundle-url` | No | — | URL to the widget IIFE bundle — triggers [bundle mode](#bundle-mode) (non-iframe) |
211
+ | `domain-key` | Bundle mode | — | Environment key: `SGTR`, `STG`, or `PROD`. Required for bundle mode on external sites |
212
+ | `theme` | No | `light` | `light` or `dark` |
213
+ | `data-email` | Public Embed only | — | User's email for identification. **Access Key Embed:** omit — identity is signed into `embed-token` |
214
+ | `embed-token` | Access Key Embed only | — | Short-lived JWT from your server. Carries signed user identifiers — no `data-*` identifier attributes needed |
215
+ | `width` | No | `100%` | CSS width |
216
+ | `height` | No | `600px` | CSS height |
217
+ | `locale` | No | — | BCP 47 locale tag (e.g. `fr-FR`) |
218
+ | `eager` | No | — | Load immediately instead of waiting until visible (presence-based boolean) |
219
+ | `auto-refresh` | No | `true` | Auto-refresh access token before expiry |
220
+ | `debug` | No | `false` | Verbose console logging |
221
+ | `custom-data` | No | — | JSON object forwarded to the widget |
222
+ | `retry-label` | No | `Retry` | Label text for the retry button on the error screen |
223
+ | `v2-api-url` | Reserved | — | Reserved for future deployments where V2 API endpoints live on a different host than `api-url`. Currently unused — safe to omit |
224
+
225
+ > **`widget-id` vs `community-id`:** The script-tag method uses `data-widget-id` which is a pre-encoded identifier (base64). The custom element method uses raw ObjectIds — `community-id` for most widget types, plus `channel-id` for channel widgets. The SDK handles the encoding internally.
226
+
227
+ ### Container `<div>` (script-tag method only)
228
+
229
+ The SDK looks for a `<div>` with `id="returning-ai-widget-{YOUR_WIDGET_ID}"`. Size the div to control the widget dimensions:
230
+
231
+ ```html
232
+ <div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
233
+ ```
234
+
235
+ Custom elements don't need a container div — they size themselves using `width` and `height` attributes (defaults: `100%` and `600px`).
236
+
237
+ ### User Identifier Attributes
238
+
239
+ > **Access Key Embed:** Do **not** put identifiers in HTML. Pass them inside the `userIdentifiers` object when exchanging your access credentials for an embed token (Step 2). Identity is cryptographically signed into the token so it cannot be tampered with from the browser.
240
+ >
241
+ > **Public Embed:** Pass any `data-*` attribute to identify the current user. Which identifiers are available is configured per community in your dashboard.
242
+
243
+ ```html
244
+ <!-- Public Embed — identifiers in HTML -->
245
+ <script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
246
+ data-widget-id="YOUR_WIDGET_ID"
247
+ data-widget-type="store"
248
+ data-email="user@example.com"
249
+ data-user-id="12345"
250
+ data-account-id="abc"
251
+ api-url="YOUR_API_URL"
252
+ widget-url="YOUR_WIDGET_URL"
253
+ ></script>
254
+ ```
255
+
256
+ In Public Embed mode, any `data-*` attribute not reserved by the SDK is forwarded to the auth API as a user identifier.
257
+
258
+ ---
259
+
260
+ ## Bundle Mode
261
+
262
+ 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.
263
+
264
+ - `widget-url` is **not** needed in bundle mode (the widget is loaded directly from `bundle-url`).
265
+ - `api-url` **is still required** — it's where the SDK sends the auth request (`/auth/serverless`).
266
+ - `domain-key` is required so the widget IIFE resolves the correct environment URLs.
267
+
268
+ Both examples below use Access Key Embed:
269
+
270
+ ```html
271
+ <!-- Script-tag approach -->
272
+ <div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:100vh"></div>
273
+ <script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
274
+ data-widget-id="YOUR_WIDGET_ID"
275
+ data-widget-type="store"
276
+ data-theme="dark"
277
+ data-embed-token="eyJ..."
278
+ data-bundle-url="YOUR_BUNDLE_URL"
279
+ data-domain-key="YOUR_DOMAIN_KEY"
280
+ api-url="YOUR_API_URL"
281
+ data-eager
282
+ ></script>
283
+
284
+ <!-- Custom element approach -->
285
+ <rai-store-widget
286
+ community-id="YOUR_COMMUNITY_ID"
287
+ api-url="YOUR_API_URL"
288
+ bundle-url="YOUR_BUNDLE_URL"
289
+ domain-key="YOUR_DOMAIN_KEY"
290
+ embed-token="eyJ..."
291
+ theme="dark"
292
+ width="100%"
293
+ height="100vh"
294
+ eager
295
+ ></rai-store-widget>
296
+ ```
297
+
298
+ Each widget type has its own IIFE bundle and global name:
299
+
300
+ | Widget type | Bundle global | Custom element tag |
301
+ |-------------|--------------|-------------------|
302
+ | `store` | `RaiStoreWidget` | `<rai-store-widget>` |
303
+ | `channel` | `RaiChannelWidget` | `<rai-channel-widget>` |
304
+ | `social` | `RaiSocialWidget` | `<rai-social-widget>` |
305
+ | `milestone` | `RaiMilestoneWidget` | `<rai-milestone-widget>` |
306
+ | `currency-view` | `RaiCurrencyWidget` | `<rai-currency-widget>` |
307
+ | `referral-conditions` | `RaiReferralWidget` | `<rai-referral-widget>` |
308
+ | `custom` | `RaiCustomWidget` | `<rai-custom-widget>` |
309
+
310
+ The bundle must export a `mount(container, config)` function on its global (e.g. `window.RaiStoreWidget.mount`).
311
+
312
+ ---
313
+
314
+ ## Framework Usage (Custom Elements)
315
+
316
+ When using a frontend framework (React, Vue, Angular), you can use custom HTML element tags instead of the script-tag method. Custom elements use raw ObjectIds (`community-id`, `channel-id`) instead of the pre-encoded `data-widget-id` — the SDK handles the encoding internally. (Exception: `<rai-custom-widget>` uses `widget-id` with a base64-encoded value — see the [attributes table](#custom-element-attributes).)
317
+
318
+ All examples below use **Access Key Embed** — pass the short-lived token your server minted into `embed-token`. For [Public Embed](#user-identifier-attributes), replace `embed-token` with `data-*` identifier attributes.
319
+
320
+ ### Plain HTML
321
+
322
+ ```html
323
+ <script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"></script>
324
+
325
+ <!-- Store widget -->
326
+ <rai-store-widget
327
+ community-id="YOUR_COMMUNITY_ID"
328
+ theme="dark"
329
+ embed-token="eyJ..."
330
+ api-url="YOUR_API_URL"
331
+ widget-url="YOUR_WIDGET_URL"
332
+ ></rai-store-widget>
333
+
334
+ <!-- Channel widget — requires both community-id AND channel-id -->
335
+ <rai-channel-widget
336
+ community-id="YOUR_COMMUNITY_ID"
337
+ channel-id="YOUR_CHANNEL_ID"
338
+ theme="dark"
339
+ embed-token="eyJ..."
340
+ api-url="YOUR_API_URL"
341
+ widget-url="YOUR_WIDGET_URL"
342
+ ></rai-channel-widget>
343
+ ```
344
+
345
+ ### React (TypeScript)
346
+
347
+ Types are included — no manual JSX declarations needed.
348
+
349
+ ```tsx
350
+ import '@returningai/widget-sdk'
351
+
352
+ export function StoreEmbed({ embedToken }: { embedToken: string }) {
353
+ return (
354
+ <rai-store-widget
355
+ community-id="YOUR_COMMUNITY_ID"
356
+ theme="dark"
357
+ embed-token={embedToken}
358
+ api-url="YOUR_API_URL"
359
+ widget-url="YOUR_WIDGET_URL"
360
+ />
361
+ )
362
+ }
363
+
364
+ export function ChannelEmbed({ embedToken }: { embedToken: string }) {
365
+ return (
366
+ <rai-channel-widget
367
+ community-id="YOUR_COMMUNITY_ID"
368
+ channel-id="YOUR_CHANNEL_ID"
369
+ theme="dark"
370
+ embed-token={embedToken}
371
+ api-url="YOUR_API_URL"
372
+ widget-url="YOUR_WIDGET_URL"
373
+ />
374
+ )
375
+ }
376
+ ```
377
+
378
+ ### Vue 3
379
+
380
+ ```vue
381
+ <script setup>
382
+ import '@returningai/widget-sdk'
383
+ defineProps({ embedToken: String })
384
+ </script>
385
+
386
+ <template>
387
+ <!-- Store widget -->
388
+ <rai-store-widget
389
+ community-id="YOUR_COMMUNITY_ID"
390
+ theme="dark"
391
+ :embed-token="embedToken"
392
+ api-url="YOUR_API_URL"
393
+ widget-url="YOUR_WIDGET_URL"
394
+ />
395
+
396
+ <!-- Channel widget -->
397
+ <rai-channel-widget
398
+ community-id="YOUR_COMMUNITY_ID"
399
+ channel-id="YOUR_CHANNEL_ID"
400
+ theme="dark"
401
+ :embed-token="embedToken"
402
+ api-url="YOUR_API_URL"
403
+ widget-url="YOUR_WIDGET_URL"
404
+ />
405
+ </template>
406
+ ```
407
+
408
+ ### Angular
409
+
410
+ ```ts
411
+ // app.module.ts
412
+ import '@returningai/widget-sdk'
413
+ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
414
+
415
+ @NgModule({ schemas: [CUSTOM_ELEMENTS_SCHEMA] })
416
+ ```
417
+
418
+ ```html
419
+ <!-- Store widget -->
420
+ <rai-store-widget
421
+ community-id="YOUR_COMMUNITY_ID"
422
+ theme="dark"
423
+ [attr.embed-token]="embedToken"
424
+ api-url="YOUR_API_URL"
425
+ widget-url="YOUR_WIDGET_URL"
426
+ ></rai-store-widget>
427
+
428
+ <!-- Channel widget -->
429
+ <rai-channel-widget
430
+ community-id="YOUR_COMMUNITY_ID"
431
+ channel-id="YOUR_CHANNEL_ID"
432
+ theme="dark"
433
+ [attr.embed-token]="embedToken"
434
+ api-url="YOUR_API_URL"
435
+ widget-url="YOUR_WIDGET_URL"
436
+ ></rai-channel-widget>
437
+ ```
438
+
439
+ ### Available custom element tags
440
+
441
+ | Tag | Required attributes | Widget type |
442
+ |-----|-------------------|-------------|
443
+ | `<rai-store-widget>` | `community-id` | Store |
444
+ | `<rai-channel-widget>` | `community-id` + `channel-id` | Channel |
445
+ | `<rai-milestone-widget>` | `community-id` | Milestone |
446
+ | `<rai-social-widget>` | `community-id` | Social |
447
+ | `<rai-currency-widget>` | `community-id` | Currency |
448
+ | `<rai-referral-widget>` | `community-id` | Referral |
449
+ | `<rai-custom-widget>` | `widget-id` (not `community-id`) | Custom |
450
+
451
+ ---
452
+
453
+ ## DOM Events
454
+
455
+ Listen for widget lifecycle events:
456
+
457
+ | Event | `detail` | Fired when |
458
+ |-------|----------|-----------|
459
+ | `rai-authenticated` | `{}` | Auth succeeded, before widget mounts |
460
+ | `rai-ready` | `{}` | `WIDGET_READY` received, loader hidden (iframe mode) |
461
+ | `rai-mounted` | `{}` | Widget bundle mounted successfully (bundle mode) |
462
+ | `rai-error` | `{ message, hint?, status? }` | Auth failed after all retries. `message` is the backend error text (e.g. `"Invalid or expired embed token"`), `hint` is the optional human-readable suggestion, `status` is the HTTP status code of the failing response. |
463
+ | `rai-logout` | `{}` | Widget logged out |
464
+ | `rai-height-change` | `{ height }` | iframe resized (after debounce) |
465
+
466
+ ```js
467
+ // Script-tag embed
468
+ const widget = document.querySelector('[data-widget-id]')
469
+
470
+ // Custom element embed
471
+ const widget = document.querySelector('rai-store-widget')
472
+
473
+ widget.addEventListener('rai-authenticated', () => { /* auth succeeded */ })
474
+ widget.addEventListener('rai-ready', () => { /* widget loaded and visible (iframe mode) */ })
475
+ widget.addEventListener('rai-mounted', () => { /* widget mounted (bundle mode) */ })
476
+ widget.addEventListener('rai-error', (e) => console.error(e.detail.status, e.detail.message, e.detail.hint))
477
+ widget.addEventListener('rai-logout', () => { /* user logged out */ })
478
+ widget.addEventListener('rai-height-change', (e) => console.log(e.detail.height))
479
+ ```
480
+
481
+ ---
482
+
483
+ ## Public API
484
+
485
+ After the SDK loads, `window.ReturningAIWidget` is available:
486
+
487
+ ```js
488
+ window.ReturningAIWidget.version // current SDK version
489
+ await window.ReturningAIWidget.reload() // re-runs auth flow and reloads widget
490
+ await window.ReturningAIWidget.logout() // clears tokens and removes widget
491
+ window.ReturningAIWidget.isAuthenticated() // boolean
492
+ window.ReturningAIWidget.getTokenInfo() // token metadata (no token values exposed)
493
+ ```
494
+
495
+ ---
496
+
497
+ ## Browser Support
498
+
499
+ Requires Custom Elements v1 + Shadow DOM v1.
500
+
501
+ | Browser | Minimum version |
502
+ |---------|----------------|
503
+ | Chrome | 67 |
504
+ | Firefox | 63 |
505
+ | Safari | 13 |