@returningai/widget-sdk 1.4.0 → 1.4.2
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/CHANGELOG.md +104 -0
- package/README.md +485 -491
- package/dist/rai-widget.iife.js +1 -1
- package/dist/rai-widget.js +141 -22
- package/dist/types/BaseWidget.d.ts +10 -0
- package/package.json +35 -33
package/README.md
CHANGED
|
@@ -1,491 +1,485 @@
|
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
**
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
data-
|
|
54
|
-
data-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
The
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
| `
|
|
88
|
-
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
92
|
-
| `
|
|
93
|
-
| `
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
<
|
|
102
|
-
|
|
103
|
-
data-widget-
|
|
104
|
-
data-
|
|
105
|
-
data-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
data-widget-
|
|
117
|
-
data-
|
|
118
|
-
data-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
<
|
|
130
|
-
|
|
131
|
-
data-widget-
|
|
132
|
-
data-
|
|
133
|
-
data-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
<
|
|
143
|
-
|
|
144
|
-
data-widget-
|
|
145
|
-
data-
|
|
146
|
-
data-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
|
180
|
-
|
|
181
|
-
| `data-
|
|
182
|
-
| `data-
|
|
183
|
-
| `api-url` | No |
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
|
188
|
-
|
|
189
|
-
| `
|
|
190
|
-
| `
|
|
191
|
-
| `
|
|
192
|
-
| `
|
|
193
|
-
| `
|
|
194
|
-
| `
|
|
195
|
-
| `data-
|
|
196
|
-
| `
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
|
201
|
-
|
|
202
|
-
| `
|
|
203
|
-
| `
|
|
204
|
-
| `
|
|
205
|
-
| `
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
>
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
-
|
|
260
|
-
- `api-url`
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
data-
|
|
271
|
-
data-
|
|
272
|
-
data-
|
|
273
|
-
data-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
|
295
|
-
|
|
296
|
-
| `
|
|
297
|
-
| `
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
<
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
|
428
|
-
|
|
429
|
-
| `<rai-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
```
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
| Browser | Minimum version |
|
|
488
|
-
|---------|----------------|
|
|
489
|
-
| Chrome | 67 |
|
|
490
|
-
| Firefox | 63 |
|
|
491
|
-
| 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
|
+
> 🛡️ **2026-05-11 security notice — refresh-token TTL for Access Key Embed.** The auth backend now issues 0-TTL refresh tokens for Access Key Embed sessions, and rejects refresh tokens issued before this date. **No SDK update required.** See [CHANGELOG.md](./CHANGELOG.md) for the full rationale and behavior changes.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
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.
|
|
12
|
+
|
|
13
|
+
**Step 1 — Create an access key pair** (one-time, from your dashboard):
|
|
14
|
+
|
|
15
|
+
Go to **Community Settings > Integrations > SDK Access** and click **Create SDK Key**. Enter a name and the allowed origins for the sites that will embed the widget.
|
|
16
|
+
|
|
17
|
+
The dashboard will display your `accessId` and `accessKey`. The `accessKey` is shown **once only** — save it securely.
|
|
18
|
+
|
|
19
|
+
Store both values securely on your server.
|
|
20
|
+
|
|
21
|
+
> **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.
|
|
22
|
+
|
|
23
|
+
**Step 2 — Exchange credentials for an embed token on every page load** (Node.js example):
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
// Runs on YOUR SERVER — never in the browser
|
|
27
|
+
const response = await fetch('https://api-v2.returning.ai/v2/api/widget-access-keys/token', {
|
|
28
|
+
method: 'POST',
|
|
29
|
+
headers: { 'Content-Type': 'application/json' },
|
|
30
|
+
body: JSON.stringify({
|
|
31
|
+
accessId: process.env.RAI_ACCESS_ID,
|
|
32
|
+
accessKey: process.env.RAI_ACCESS_KEY,
|
|
33
|
+
// userIdentifiers keys MUST be `data-*` prefixed — they match the
|
|
34
|
+
// `dataAttribute` value of each field configured in your widget's
|
|
35
|
+
// User Identifier Fields (e.g. the dashboard stores "data-email",
|
|
36
|
+
// "data-user-id", not bare "email"/"userId").
|
|
37
|
+
userIdentifiers: {
|
|
38
|
+
'data-user-id': [YourUserObject].id,
|
|
39
|
+
'data-email': [YourUserObject].email,
|
|
40
|
+
// ...any other fields configured for this widget
|
|
41
|
+
},
|
|
42
|
+
}),
|
|
43
|
+
})
|
|
44
|
+
const { data } = await response.json()
|
|
45
|
+
// data.embedToken expires in 15 minutes and carries signed identity claims
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Step 3 — Embed** (only the short-lived token appears in HTML — no identity attributes):
|
|
49
|
+
|
|
50
|
+
```html
|
|
51
|
+
<div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
|
|
52
|
+
<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
|
|
53
|
+
data-widget-id="YOUR_WIDGET_ID"
|
|
54
|
+
data-widget-type="store"
|
|
55
|
+
data-theme="dark"
|
|
56
|
+
data-embed-token="eyJ..."
|
|
57
|
+
widget-url="YOUR_WIDGET_URL"
|
|
58
|
+
></script>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The SDK uses ReturningAI endpoints by default. `api-url` is optional and only needed when you want to override the widget auth server.
|
|
62
|
+
|
|
63
|
+
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.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Install
|
|
68
|
+
|
|
69
|
+
### CDN (no install needed)
|
|
70
|
+
|
|
71
|
+
```html
|
|
72
|
+
<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"></script>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### npm / yarn / pnpm
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npm install @returningai/widget-sdk
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Widget Types
|
|
84
|
+
|
|
85
|
+
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.
|
|
86
|
+
|
|
87
|
+
| `data-widget-type` | Description |
|
|
88
|
+
|---------------------|-------------|
|
|
89
|
+
| `store` | Store / rewards widget |
|
|
90
|
+
| `channel` | Channel widget |
|
|
91
|
+
| `milestone` | Milestone widget |
|
|
92
|
+
| `currency-view` | Currency widget |
|
|
93
|
+
| `referral-conditions` | Referral widget |
|
|
94
|
+
| `custom` | Custom widget |
|
|
95
|
+
|
|
96
|
+
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.
|
|
97
|
+
|
|
98
|
+
### Store widget
|
|
99
|
+
|
|
100
|
+
```html
|
|
101
|
+
<div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
|
|
102
|
+
<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
|
|
103
|
+
data-widget-id="YOUR_WIDGET_ID"
|
|
104
|
+
data-widget-type="store"
|
|
105
|
+
data-theme="dark"
|
|
106
|
+
data-embed-token="eyJ..."
|
|
107
|
+
widget-url="YOUR_WIDGET_URL"
|
|
108
|
+
></script>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Channel widget
|
|
112
|
+
|
|
113
|
+
```html
|
|
114
|
+
<div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
|
|
115
|
+
<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
|
|
116
|
+
data-widget-id="YOUR_WIDGET_ID"
|
|
117
|
+
data-widget-type="channel"
|
|
118
|
+
data-theme="dark"
|
|
119
|
+
data-embed-token="eyJ..."
|
|
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
|
+
### Milestone 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="milestone"
|
|
133
|
+
data-theme="dark"
|
|
134
|
+
data-embed-token="eyJ..."
|
|
135
|
+
widget-url="YOUR_WIDGET_URL"
|
|
136
|
+
></script>
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Custom widget
|
|
140
|
+
|
|
141
|
+
```html
|
|
142
|
+
<div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
|
|
143
|
+
<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
|
|
144
|
+
data-widget-id="YOUR_WIDGET_ID"
|
|
145
|
+
data-widget-type="custom"
|
|
146
|
+
data-theme="dark"
|
|
147
|
+
data-embed-token="eyJ..."
|
|
148
|
+
widget-url="YOUR_WIDGET_URL"
|
|
149
|
+
></script>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
> **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.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Attributes
|
|
157
|
+
|
|
158
|
+
There are two ways to identify your widget:
|
|
159
|
+
|
|
160
|
+
1. **Script-tag embed:** Use `data-widget-id` (a pre-encoded ID from your dashboard) on the `<script>` tag
|
|
161
|
+
2. **Custom element embed:** Use `community-id` (and `channel-id` for channel widgets) on the custom element tag
|
|
162
|
+
|
|
163
|
+
All attributes can be provided with or without the `data-` prefix — both work.
|
|
164
|
+
|
|
165
|
+
### Script-tag attributes
|
|
166
|
+
|
|
167
|
+
| Attribute | Required | Default | Description |
|
|
168
|
+
|-----------|----------|---------|-------------|
|
|
169
|
+
| `data-widget-id` | **Yes** | — | Your widget ID (from dashboard) |
|
|
170
|
+
| `data-widget-type` | **Yes** | `store` | Widget type (see table above) |
|
|
171
|
+
| `api-url` | No | ReturningAI widget auth server | Optional widget auth server override |
|
|
172
|
+
| `widget-url` | **Yes**\* | — | Widget URL (from dashboard). \*Not required when using `data-bundle-url` |
|
|
173
|
+
| `data-bundle-url` | No | — | URL to the widget IIFE bundle — triggers [bundle mode](#bundle-mode) (non-iframe) |
|
|
174
|
+
| `data-theme` | No | `light` | `light` or `dark` |
|
|
175
|
+
| `data-email` | Public Embed only | — | User's email for identification. **Access Key Embed:** omit — identity is signed into `data-embed-token` |
|
|
176
|
+
| `data-embed-token` | Access Key Embed only | — | Short-lived JWT from your server. Carries signed user identifiers — no `data-*` identifier attributes needed |
|
|
177
|
+
| `data-locale` | No | — | BCP 47 locale tag (e.g. `fr-FR`) |
|
|
178
|
+
| `data-eager` | No | — | Load immediately instead of waiting until visible (presence-based boolean) |
|
|
179
|
+
| `data-auto-refresh` | No | `true` | Auto-refresh access token before expiry |
|
|
180
|
+
| `data-debug` | No | `false` | Verbose console logging |
|
|
181
|
+
| `data-custom-data` | No | — | JSON object forwarded to the widget |
|
|
182
|
+
| `data-retry-label` | No | `Retry` | Label text for the retry button on the error screen |
|
|
183
|
+
| `data-v2-api-url` | No | ReturningAI V2 API | Optional V2 API override for bundle mode |
|
|
184
|
+
|
|
185
|
+
### Custom element attributes
|
|
186
|
+
|
|
187
|
+
| Attribute | Required | Default | Description |
|
|
188
|
+
|-----------|----------|---------|-------------|
|
|
189
|
+
| `community-id` | **Yes**\* | — | Your community ID (raw ObjectId from dashboard). \*Custom widgets use `widget-id` instead |
|
|
190
|
+
| `channel-id` | **Channel only** | — | Channel ID (raw ObjectId) — required for `<rai-channel-widget>` |
|
|
191
|
+
| `api-url` | No | ReturningAI widget auth server | Optional widget auth server override |
|
|
192
|
+
| `widget-url` | **Yes**\* | — | Widget URL (from dashboard). \*Not required when using `bundle-url` |
|
|
193
|
+
| `bundle-url` | No | — | URL to the widget IIFE bundle — triggers [bundle mode](#bundle-mode) (non-iframe) |
|
|
194
|
+
| `theme` | No | `light` | `light` or `dark` |
|
|
195
|
+
| `data-email` | Public Embed only | — | User's email for identification. **Access Key Embed:** omit — identity is signed into `embed-token` |
|
|
196
|
+
| `embed-token` | Access Key Embed only | — | Short-lived JWT from your server. Carries signed user identifiers — no `data-*` identifier attributes needed |
|
|
197
|
+
| `width` | No | `100%` | CSS width |
|
|
198
|
+
| `height` | No | `600px` | CSS height |
|
|
199
|
+
| `locale` | No | — | BCP 47 locale tag (e.g. `fr-FR`) |
|
|
200
|
+
| `eager` | No | — | Load immediately instead of waiting until visible (presence-based boolean) |
|
|
201
|
+
| `auto-refresh` | No | `true` | Auto-refresh access token before expiry |
|
|
202
|
+
| `debug` | No | `false` | Verbose console logging |
|
|
203
|
+
| `custom-data` | No | — | JSON object forwarded to the widget |
|
|
204
|
+
| `retry-label` | No | `Retry` | Label text for the retry button on the error screen |
|
|
205
|
+
| `v2-api-url` | No | ReturningAI V2 API | Optional V2 API override for bundle mode |
|
|
206
|
+
|
|
207
|
+
> **`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.
|
|
208
|
+
|
|
209
|
+
### Container `<div>` (script-tag method only)
|
|
210
|
+
|
|
211
|
+
The SDK looks for a `<div>` with `id="returning-ai-widget-{YOUR_WIDGET_ID}"`. Size the div to control the widget dimensions:
|
|
212
|
+
|
|
213
|
+
```html
|
|
214
|
+
<div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Custom elements don't need a container div — they size themselves using `width` and `height` attributes (defaults: `100%` and `600px`).
|
|
218
|
+
|
|
219
|
+
### User Identifier Attributes
|
|
220
|
+
|
|
221
|
+
> **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.
|
|
222
|
+
>
|
|
223
|
+
> **Public Embed:** Pass any `data-*` attribute to identify the current user. Which identifiers are available is configured per community in your dashboard.
|
|
224
|
+
|
|
225
|
+
```html
|
|
226
|
+
<!-- Public Embed — identifiers in HTML -->
|
|
227
|
+
<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
|
|
228
|
+
data-widget-id="YOUR_WIDGET_ID"
|
|
229
|
+
data-widget-type="store"
|
|
230
|
+
data-email="user@example.com"
|
|
231
|
+
data-user-id="12345"
|
|
232
|
+
data-member-id="abc"
|
|
233
|
+
widget-url="YOUR_WIDGET_URL"
|
|
234
|
+
></script>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
In Public Embed mode, any `data-*` attribute not reserved by the SDK is forwarded to the auth API as a user identifier.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Endpoint URL Overrides
|
|
242
|
+
|
|
243
|
+
The SDK uses ReturningAI endpoints by default.
|
|
244
|
+
|
|
245
|
+
These fields are optional. Set them only when you need to override the default endpoints:
|
|
246
|
+
|
|
247
|
+
- `api-url` / `data-api-url` — overrides the widget auth server used for SDK auth, refresh, logout, and error settings.
|
|
248
|
+
- `v2-api-url` / `data-v2-api-url` — overrides the V2 API base URL used by bundle mode.
|
|
249
|
+
|
|
250
|
+
These endpoint overrides do not replace `widget-url` or `bundle-url`; use the widget and bundle URLs from your dashboard.
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Bundle Mode
|
|
255
|
+
|
|
256
|
+
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.
|
|
257
|
+
|
|
258
|
+
- `widget-url` is **not** needed in bundle mode (the widget is loaded directly from `bundle-url`).
|
|
259
|
+
- ReturningAI endpoints are used by default.
|
|
260
|
+
- `api-url` and `v2-api-url` are optional endpoint URL overrides.
|
|
261
|
+
|
|
262
|
+
Both examples below use Access Key Embed and the default endpoints.
|
|
263
|
+
|
|
264
|
+
```html
|
|
265
|
+
<!-- Script-tag approach -->
|
|
266
|
+
<div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:100vh"></div>
|
|
267
|
+
<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
|
|
268
|
+
data-widget-id="YOUR_WIDGET_ID"
|
|
269
|
+
data-widget-type="store"
|
|
270
|
+
data-theme="dark"
|
|
271
|
+
data-embed-token="eyJ..."
|
|
272
|
+
data-bundle-url="YOUR_BUNDLE_URL"
|
|
273
|
+
data-eager
|
|
274
|
+
></script>
|
|
275
|
+
|
|
276
|
+
<!-- Custom element approach -->
|
|
277
|
+
<rai-store-widget
|
|
278
|
+
community-id="YOUR_COMMUNITY_ID"
|
|
279
|
+
bundle-url="YOUR_BUNDLE_URL"
|
|
280
|
+
embed-token="eyJ..."
|
|
281
|
+
theme="dark"
|
|
282
|
+
width="100%"
|
|
283
|
+
height="100vh"
|
|
284
|
+
eager
|
|
285
|
+
></rai-store-widget>
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Each widget type has its own IIFE bundle and global name:
|
|
289
|
+
|
|
290
|
+
| Widget type | Bundle global | Custom element tag |
|
|
291
|
+
|-------------|--------------|-------------------|
|
|
292
|
+
| `store` | `RaiStoreWidget` | `<rai-store-widget>` |
|
|
293
|
+
| `channel` | `RaiChannelWidget` | `<rai-channel-widget>` |
|
|
294
|
+
| `milestone` | `RaiMilestoneWidget` | `<rai-milestone-widget>` |
|
|
295
|
+
| `currency-view` | `RaiCurrencyWidget` | `<rai-currency-widget>` |
|
|
296
|
+
| `referral-conditions` | `RaiReferralWidget` | `<rai-referral-widget>` |
|
|
297
|
+
| `custom` | `RaiCustomWidget` | `<rai-custom-widget>` |
|
|
298
|
+
|
|
299
|
+
The bundle must export a `mount(container, config)` function on its global (e.g. `window.RaiStoreWidget.mount`).
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## Framework Usage (Custom Elements)
|
|
304
|
+
|
|
305
|
+
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).)
|
|
306
|
+
|
|
307
|
+
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.
|
|
308
|
+
|
|
309
|
+
### Plain HTML
|
|
310
|
+
|
|
311
|
+
```html
|
|
312
|
+
<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"></script>
|
|
313
|
+
|
|
314
|
+
<!-- Store widget -->
|
|
315
|
+
<rai-store-widget
|
|
316
|
+
community-id="YOUR_COMMUNITY_ID"
|
|
317
|
+
theme="dark"
|
|
318
|
+
embed-token="eyJ..."
|
|
319
|
+
widget-url="YOUR_WIDGET_URL"
|
|
320
|
+
></rai-store-widget>
|
|
321
|
+
|
|
322
|
+
<!-- Channel widget — requires both community-id AND channel-id -->
|
|
323
|
+
<rai-channel-widget
|
|
324
|
+
community-id="YOUR_COMMUNITY_ID"
|
|
325
|
+
channel-id="YOUR_CHANNEL_ID"
|
|
326
|
+
theme="dark"
|
|
327
|
+
embed-token="eyJ..."
|
|
328
|
+
widget-url="YOUR_WIDGET_URL"
|
|
329
|
+
></rai-channel-widget>
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### React (TypeScript)
|
|
333
|
+
|
|
334
|
+
Types are included — no manual JSX declarations needed.
|
|
335
|
+
|
|
336
|
+
```tsx
|
|
337
|
+
import '@returningai/widget-sdk'
|
|
338
|
+
|
|
339
|
+
export function StoreEmbed({ embedToken }: { embedToken: string }) {
|
|
340
|
+
return (
|
|
341
|
+
<rai-store-widget
|
|
342
|
+
community-id="YOUR_COMMUNITY_ID"
|
|
343
|
+
theme="dark"
|
|
344
|
+
embed-token={embedToken}
|
|
345
|
+
widget-url="YOUR_WIDGET_URL"
|
|
346
|
+
/>
|
|
347
|
+
)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export function ChannelEmbed({ embedToken }: { embedToken: string }) {
|
|
351
|
+
return (
|
|
352
|
+
<rai-channel-widget
|
|
353
|
+
community-id="YOUR_COMMUNITY_ID"
|
|
354
|
+
channel-id="YOUR_CHANNEL_ID"
|
|
355
|
+
theme="dark"
|
|
356
|
+
embed-token={embedToken}
|
|
357
|
+
widget-url="YOUR_WIDGET_URL"
|
|
358
|
+
/>
|
|
359
|
+
)
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Vue 3
|
|
364
|
+
|
|
365
|
+
```vue
|
|
366
|
+
<script setup>
|
|
367
|
+
import '@returningai/widget-sdk'
|
|
368
|
+
defineProps({ embedToken: String })
|
|
369
|
+
</script>
|
|
370
|
+
|
|
371
|
+
<template>
|
|
372
|
+
<!-- Store widget -->
|
|
373
|
+
<rai-store-widget
|
|
374
|
+
community-id="YOUR_COMMUNITY_ID"
|
|
375
|
+
theme="dark"
|
|
376
|
+
:embed-token="embedToken"
|
|
377
|
+
widget-url="YOUR_WIDGET_URL"
|
|
378
|
+
/>
|
|
379
|
+
|
|
380
|
+
<!-- Channel widget -->
|
|
381
|
+
<rai-channel-widget
|
|
382
|
+
community-id="YOUR_COMMUNITY_ID"
|
|
383
|
+
channel-id="YOUR_CHANNEL_ID"
|
|
384
|
+
theme="dark"
|
|
385
|
+
:embed-token="embedToken"
|
|
386
|
+
widget-url="YOUR_WIDGET_URL"
|
|
387
|
+
/>
|
|
388
|
+
</template>
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### Angular
|
|
392
|
+
|
|
393
|
+
```ts
|
|
394
|
+
// app.module.ts
|
|
395
|
+
import '@returningai/widget-sdk'
|
|
396
|
+
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
|
|
397
|
+
|
|
398
|
+
@NgModule({ schemas: [CUSTOM_ELEMENTS_SCHEMA] })
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
```html
|
|
402
|
+
<!-- Store widget -->
|
|
403
|
+
<rai-store-widget
|
|
404
|
+
community-id="YOUR_COMMUNITY_ID"
|
|
405
|
+
theme="dark"
|
|
406
|
+
[attr.embed-token]="embedToken"
|
|
407
|
+
widget-url="YOUR_WIDGET_URL"
|
|
408
|
+
></rai-store-widget>
|
|
409
|
+
|
|
410
|
+
<!-- Channel widget -->
|
|
411
|
+
<rai-channel-widget
|
|
412
|
+
community-id="YOUR_COMMUNITY_ID"
|
|
413
|
+
channel-id="YOUR_CHANNEL_ID"
|
|
414
|
+
theme="dark"
|
|
415
|
+
[attr.embed-token]="embedToken"
|
|
416
|
+
widget-url="YOUR_WIDGET_URL"
|
|
417
|
+
></rai-channel-widget>
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### Available custom element tags
|
|
421
|
+
|
|
422
|
+
| Tag | Required attributes | Widget type |
|
|
423
|
+
|-----|-------------------|-------------|
|
|
424
|
+
| `<rai-store-widget>` | `community-id` | Store |
|
|
425
|
+
| `<rai-channel-widget>` | `community-id` + `channel-id` | Channel |
|
|
426
|
+
| `<rai-milestone-widget>` | `community-id` | Milestone |
|
|
427
|
+
| `<rai-currency-widget>` | `community-id` | Currency |
|
|
428
|
+
| `<rai-referral-widget>` | `community-id` | Referral |
|
|
429
|
+
| `<rai-custom-widget>` | `widget-id` (not `community-id`) | Custom |
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## DOM Events
|
|
434
|
+
|
|
435
|
+
Listen for widget lifecycle events:
|
|
436
|
+
|
|
437
|
+
| Event | `detail` | Fired when |
|
|
438
|
+
|-------|----------|-----------|
|
|
439
|
+
| `rai-authenticated` | `{}` | Auth succeeded, before widget mounts |
|
|
440
|
+
| `rai-ready` | `{}` | `WIDGET_READY` received, loader hidden (iframe mode) |
|
|
441
|
+
| `rai-mounted` | `{}` | Widget bundle mounted successfully (bundle mode) |
|
|
442
|
+
| `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. |
|
|
443
|
+
| `rai-logout` | `{}` | Widget logged out |
|
|
444
|
+
| `rai-height-change` | `{ height }` | iframe resized (after debounce) |
|
|
445
|
+
|
|
446
|
+
```js
|
|
447
|
+
// Script-tag embed
|
|
448
|
+
const widget = document.querySelector('[data-widget-id]')
|
|
449
|
+
|
|
450
|
+
// Custom element embed
|
|
451
|
+
const widget = document.querySelector('rai-store-widget')
|
|
452
|
+
|
|
453
|
+
widget.addEventListener('rai-authenticated', () => { /* auth succeeded */ })
|
|
454
|
+
widget.addEventListener('rai-ready', () => { /* widget loaded and visible (iframe mode) */ })
|
|
455
|
+
widget.addEventListener('rai-mounted', () => { /* widget mounted (bundle mode) */ })
|
|
456
|
+
widget.addEventListener('rai-error', (e) => console.error(e.detail.status, e.detail.message, e.detail.hint))
|
|
457
|
+
widget.addEventListener('rai-logout', () => { /* user logged out */ })
|
|
458
|
+
widget.addEventListener('rai-height-change', (e) => console.log(e.detail.height))
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
## Public API
|
|
464
|
+
|
|
465
|
+
After the SDK loads, `window.ReturningAIWidget` is available:
|
|
466
|
+
|
|
467
|
+
```js
|
|
468
|
+
window.ReturningAIWidget.version // current SDK version
|
|
469
|
+
await window.ReturningAIWidget.reload() // re-runs auth flow and reloads widget
|
|
470
|
+
await window.ReturningAIWidget.logout() // clears tokens and removes widget
|
|
471
|
+
window.ReturningAIWidget.isAuthenticated() // boolean
|
|
472
|
+
window.ReturningAIWidget.getTokenInfo() // token metadata (no token values exposed)
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
## Browser Support
|
|
478
|
+
|
|
479
|
+
Requires Custom Elements v1 + Shadow DOM v1.
|
|
480
|
+
|
|
481
|
+
| Browser | Minimum version |
|
|
482
|
+
|---------|----------------|
|
|
483
|
+
| Chrome | 67 |
|
|
484
|
+
| Firefox | 63 |
|
|
485
|
+
| Safari | 13 |
|