@returningai/widget-sdk 1.2.16 → 1.4.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
@@ -10,12 +10,14 @@ Your access credentials stay on your server. Your server exchanges them for a **
10
10
 
11
11
  **Step 1 — Create an access key pair** (one-time, from your dashboard):
12
12
 
13
- Go to **Community Settings > Integrations > SDK Access** and click **Create SDK Key**. Enter a name and optional allowed origins.
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
14
 
15
15
  The dashboard will display your `accessId` and `accessKey`. The `accessKey` is shown **once only** — save it securely.
16
16
 
17
17
  Store both values in your server environment variables.
18
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
+
19
21
  **Step 2 — Exchange credentials for an embed token on every page load** (Node.js example):
20
22
 
21
23
  ```js
@@ -26,13 +28,22 @@ const response = await fetch('https://YOUR_API_URL/v2/api/widget-access-keys/tok
26
28
  body: JSON.stringify({
27
29
  accessId: process.env.RAI_ACCESS_ID,
28
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
+ },
29
40
  }),
30
41
  })
31
42
  const { data } = await response.json()
32
- // data.embedToken expires in 15 minutes
43
+ // data.embedToken expires in 15 minutes and carries signed identity claims
33
44
  ```
34
45
 
35
- **Step 3 — Embed** (only the short-lived token appears in HTML):
46
+ **Step 3 — Embed** (only the short-lived token appears in HTML — no identity attributes):
36
47
 
37
48
  ```html
38
49
  <div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
@@ -40,14 +51,14 @@ const { data } = await response.json()
40
51
  data-widget-id="YOUR_WIDGET_ID"
41
52
  data-widget-type="store"
42
53
  data-theme="dark"
43
- data-email="user@example.com"
44
54
  data-embed-token="eyJ..."
45
- api-url="YOUR_API_URL"
46
55
  widget-url="YOUR_WIDGET_URL"
47
56
  ></script>
48
57
  ```
49
58
 
50
- 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.
59
+ `api-url` is optional. The SDK resolves the widget server from `domain-key` when provided and uses production by default; pass `api-url` only if you need to override the widget auth server.
60
+
61
+ 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.
51
62
 
52
63
  ---
53
64
 
@@ -81,6 +92,8 @@ Set the `data-widget-type` attribute to choose which widget to display. The Widg
81
92
  | `referral-conditions` | Referral widget |
82
93
  | `custom` | Custom widget |
83
94
 
95
+ 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.
96
+
84
97
  ### Store widget
85
98
 
86
99
  ```html
@@ -89,8 +102,7 @@ Set the `data-widget-type` attribute to choose which widget to display. The Widg
89
102
  data-widget-id="YOUR_WIDGET_ID"
90
103
  data-widget-type="store"
91
104
  data-theme="dark"
92
- data-email="user@example.com"
93
- api-url="YOUR_API_URL"
105
+ data-embed-token="eyJ..."
94
106
  widget-url="YOUR_WIDGET_URL"
95
107
  ></script>
96
108
  ```
@@ -103,8 +115,7 @@ Set the `data-widget-type` attribute to choose which widget to display. The Widg
103
115
  data-widget-id="YOUR_WIDGET_ID"
104
116
  data-widget-type="channel"
105
117
  data-theme="dark"
106
- data-email="user@example.com"
107
- api-url="YOUR_API_URL"
118
+ data-embed-token="eyJ..."
108
119
  widget-url="YOUR_WIDGET_URL"
109
120
  ></script>
110
121
  ```
@@ -119,8 +130,7 @@ Set the `data-widget-type` attribute to choose which widget to display. The Widg
119
130
  data-widget-id="YOUR_WIDGET_ID"
120
131
  data-widget-type="social"
121
132
  data-theme="dark"
122
- data-email="user@example.com"
123
- api-url="YOUR_API_URL"
133
+ data-embed-token="eyJ..."
124
134
  widget-url="YOUR_WIDGET_URL"
125
135
  ></script>
126
136
  ```
@@ -133,8 +143,7 @@ Set the `data-widget-type` attribute to choose which widget to display. The Widg
133
143
  data-widget-id="YOUR_WIDGET_ID"
134
144
  data-widget-type="milestone"
135
145
  data-theme="dark"
136
- data-email="user@example.com"
137
- api-url="YOUR_API_URL"
146
+ data-embed-token="eyJ..."
138
147
  widget-url="YOUR_WIDGET_URL"
139
148
  ></script>
140
149
  ```
@@ -147,12 +156,13 @@ Set the `data-widget-type` attribute to choose which widget to display. The Widg
147
156
  data-widget-id="YOUR_WIDGET_ID"
148
157
  data-widget-type="custom"
149
158
  data-theme="dark"
150
- data-email="user@example.com"
151
- api-url="YOUR_API_URL"
159
+ data-embed-token="eyJ..."
152
160
  widget-url="YOUR_WIDGET_URL"
153
161
  ></script>
154
162
  ```
155
163
 
164
+ > **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.
165
+
156
166
  ---
157
167
 
158
168
  ## Attributes
@@ -170,18 +180,20 @@ All attributes can be provided with or without the `data-` prefix — both work.
170
180
  |-----------|----------|---------|-------------|
171
181
  | `data-widget-id` | **Yes** | — | Your widget ID (from dashboard) |
172
182
  | `data-widget-type` | **Yes** | `store` | Widget type (see table above) |
173
- | `api-url` | **Yes**\* | — | Auth API base URL (from dashboard) |
183
+ | `api-url` | No | Production widget server | Optional widget auth server override. Defaults from `domain-key` when provided |
174
184
  | `widget-url` | **Yes**\* | — | Widget URL (from dashboard). \*Not required when using `data-bundle-url` |
175
185
  | `data-bundle-url` | No | — | URL to the widget IIFE bundle — triggers [bundle mode](#bundle-mode) (non-iframe) |
176
- | `data-domain-key` | Bundle mode | — | Environment key: `SGTR`, `STG`, or `PROD`. Required for bundle mode on external sites |
186
+ | `data-domain-key` | No | `PROD` | Environment key: `SGTR`, `STG`, or `PROD`. Used to resolve default environment URLs when overrides are omitted |
177
187
  | `data-theme` | No | `light` | `light` or `dark` |
178
- | `data-email` | Recommended | — | User's email for identification |
179
- | `data-embed-token` | Access Key only | — | Short-lived token from your server |
188
+ | `data-email` | Public Embed only | — | User's email for identification. **Access Key Embed:** omit — identity is signed into `data-embed-token` |
189
+ | `data-embed-token` | Access Key Embed only | — | Short-lived JWT from your server. Carries signed user identifiers — no `data-*` identifier attributes needed |
180
190
  | `data-locale` | No | — | BCP 47 locale tag (e.g. `fr-FR`) |
181
- | `data-eager` | No | — | Load immediately instead of waiting until visible |
191
+ | `data-eager` | No | — | Load immediately instead of waiting until visible (presence-based boolean) |
182
192
  | `data-auto-refresh` | No | `true` | Auto-refresh access token before expiry |
183
193
  | `data-debug` | No | `false` | Verbose console logging |
184
194
  | `data-custom-data` | No | — | JSON object forwarded to the widget |
195
+ | `data-retry-label` | No | `Retry` | Label text for the retry button on the error screen |
196
+ | `data-v2-api-url` | No | Domain-key default | Optional V2 API override for bundle mode. Safe to omit for standard environments |
185
197
 
186
198
  ### Custom element attributes
187
199
 
@@ -189,20 +201,22 @@ All attributes can be provided with or without the `data-` prefix — both work.
189
201
  |-----------|----------|---------|-------------|
190
202
  | `community-id` | **Yes**\* | — | Your community ID (raw ObjectId from dashboard). \*Custom widgets use `widget-id` instead |
191
203
  | `channel-id` | **Channel only** | — | Channel ID (raw ObjectId) — required for `<rai-channel-widget>` |
192
- | `api-url` | **Yes** | — | Auth API base URL (from dashboard) |
204
+ | `api-url` | No | Production widget server | Optional widget auth server override. Defaults from `domain-key` when provided |
193
205
  | `widget-url` | **Yes**\* | — | Widget URL (from dashboard). \*Not required when using `bundle-url` |
194
206
  | `bundle-url` | No | — | URL to the widget IIFE bundle — triggers [bundle mode](#bundle-mode) (non-iframe) |
195
- | `domain-key` | Bundle mode | — | Environment key: `SGTR`, `STG`, or `PROD`. Required for bundle mode on external sites |
207
+ | `domain-key` | No | `PROD` | Environment key: `SGTR`, `STG`, or `PROD`. Used to resolve default environment URLs when overrides are omitted |
196
208
  | `theme` | No | `light` | `light` or `dark` |
197
- | `data-email` | Recommended | — | User's email for identification |
198
- | `embed-token` | Access Key only | — | Short-lived token from your server |
209
+ | `data-email` | Public Embed only | — | User's email for identification. **Access Key Embed:** omit — identity is signed into `embed-token` |
210
+ | `embed-token` | Access Key Embed only | — | Short-lived JWT from your server. Carries signed user identifiers — no `data-*` identifier attributes needed |
199
211
  | `width` | No | `100%` | CSS width |
200
212
  | `height` | No | `600px` | CSS height |
201
213
  | `locale` | No | — | BCP 47 locale tag (e.g. `fr-FR`) |
202
- | `eager` | No | — | Load immediately instead of waiting until visible |
214
+ | `eager` | No | — | Load immediately instead of waiting until visible (presence-based boolean) |
203
215
  | `auto-refresh` | No | `true` | Auto-refresh access token before expiry |
204
216
  | `debug` | No | `false` | Verbose console logging |
205
217
  | `custom-data` | No | — | JSON object forwarded to the widget |
218
+ | `retry-label` | No | `Retry` | Label text for the retry button on the error screen |
219
+ | `v2-api-url` | No | Domain-key default | Optional V2 API override for bundle mode. Safe to omit for standard environments |
206
220
 
207
221
  > **`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
222
 
@@ -218,21 +232,23 @@ Custom elements don't need a container div — they size themselves using `width
218
232
 
219
233
  ### User Identifier Attributes
220
234
 
221
- Pass any `data-*` attribute to identify the current user. Which identifiers are available is configured per community in your dashboard.
235
+ > **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.
236
+ >
237
+ > **Public Embed:** Pass any `data-*` attribute to identify the current user. Which identifiers are available is configured per community in your dashboard.
222
238
 
223
239
  ```html
240
+ <!-- Public Embed — identifiers in HTML -->
224
241
  <script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
225
242
  data-widget-id="YOUR_WIDGET_ID"
226
243
  data-widget-type="store"
227
244
  data-email="user@example.com"
228
245
  data-user-id="12345"
229
246
  data-account-id="abc"
230
- api-url="YOUR_API_URL"
231
247
  widget-url="YOUR_WIDGET_URL"
232
248
  ></script>
233
249
  ```
234
250
 
235
- Any `data-*` attribute not reserved by the SDK is forwarded to the auth API as a user identifier.
251
+ In Public Embed mode, any `data-*` attribute not reserved by the SDK is forwarded to the auth API as a user identifier.
236
252
 
237
253
  ---
238
254
 
@@ -240,7 +256,12 @@ Any `data-*` attribute not reserved by the SDK is forwarded to the auth API as a
240
256
 
241
257
  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.
242
258
 
243
- `widget-url` is not needed in bundle mode. `domain-key` is required so the widget resolves the correct environment URLs.
259
+ - `widget-url` is **not** needed in bundle mode (the widget is loaded directly from `bundle-url`).
260
+ - `api-url` is optional. The SDK resolves the widget auth server from `domain-key` when provided, or defaults to production.
261
+ - `domain-key` is optional. When omitted, the SDK defaults to production (`PROD`).
262
+ - Environment URL variables such as `window.api_url` are optional overrides. Standard `SGTR`, `STG`, and `PROD` environments resolve from `domain-key`.
263
+
264
+ Both examples below use Access Key Embed and target staging. Omit `domain-key` for production.
244
265
 
245
266
  ```html
246
267
  <!-- Script-tag approach -->
@@ -249,20 +270,18 @@ When you set the `bundle-url` attribute, the widget renders directly in the page
249
270
  data-widget-id="YOUR_WIDGET_ID"
250
271
  data-widget-type="store"
251
272
  data-theme="dark"
252
- data-email="user@example.com"
273
+ data-embed-token="eyJ..."
253
274
  data-bundle-url="YOUR_BUNDLE_URL"
254
- data-domain-key="YOUR_DOMAIN_KEY"
255
- api-url="YOUR_API_URL"
275
+ data-domain-key="STG"
256
276
  data-eager
257
277
  ></script>
258
278
 
259
279
  <!-- Custom element approach -->
260
280
  <rai-store-widget
261
281
  community-id="YOUR_COMMUNITY_ID"
262
- api-url="YOUR_API_URL"
263
282
  bundle-url="YOUR_BUNDLE_URL"
264
- domain-key="YOUR_DOMAIN_KEY"
265
- data-email="user@example.com"
283
+ domain-key="STG"
284
+ embed-token="eyJ..."
266
285
  theme="dark"
267
286
  width="100%"
268
287
  height="100vh"
@@ -288,7 +307,9 @@ The bundle must export a `mount(container, config)` function on its global (e.g.
288
307
 
289
308
  ## Framework Usage (Custom Elements)
290
309
 
291
- 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`.
310
+ 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).)
311
+
312
+ 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.
292
313
 
293
314
  ### Plain HTML
294
315
 
@@ -299,8 +320,7 @@ When using a frontend framework (React, Vue, Angular), you can use custom HTML e
299
320
  <rai-store-widget
300
321
  community-id="YOUR_COMMUNITY_ID"
301
322
  theme="dark"
302
- data-email="user@example.com"
303
- api-url="YOUR_API_URL"
323
+ embed-token="eyJ..."
304
324
  widget-url="YOUR_WIDGET_URL"
305
325
  ></rai-store-widget>
306
326
 
@@ -309,8 +329,7 @@ When using a frontend framework (React, Vue, Angular), you can use custom HTML e
309
329
  community-id="YOUR_COMMUNITY_ID"
310
330
  channel-id="YOUR_CHANNEL_ID"
311
331
  theme="dark"
312
- data-email="user@example.com"
313
- api-url="YOUR_API_URL"
332
+ embed-token="eyJ..."
314
333
  widget-url="YOUR_WIDGET_URL"
315
334
  ></rai-channel-widget>
316
335
  ```
@@ -322,26 +341,24 @@ Types are included — no manual JSX declarations needed.
322
341
  ```tsx
323
342
  import '@returningai/widget-sdk'
324
343
 
325
- export function StoreEmbed({ email }: { email: string }) {
344
+ export function StoreEmbed({ embedToken }: { embedToken: string }) {
326
345
  return (
327
346
  <rai-store-widget
328
347
  community-id="YOUR_COMMUNITY_ID"
329
348
  theme="dark"
330
- data-email={email}
331
- api-url="YOUR_API_URL"
349
+ embed-token={embedToken}
332
350
  widget-url="YOUR_WIDGET_URL"
333
351
  />
334
352
  )
335
353
  }
336
354
 
337
- export function ChannelEmbed({ email }: { email: string }) {
355
+ export function ChannelEmbed({ embedToken }: { embedToken: string }) {
338
356
  return (
339
357
  <rai-channel-widget
340
358
  community-id="YOUR_COMMUNITY_ID"
341
359
  channel-id="YOUR_CHANNEL_ID"
342
360
  theme="dark"
343
- data-email={email}
344
- api-url="YOUR_API_URL"
361
+ embed-token={embedToken}
345
362
  widget-url="YOUR_WIDGET_URL"
346
363
  />
347
364
  )
@@ -353,6 +370,7 @@ export function ChannelEmbed({ email }: { email: string }) {
353
370
  ```vue
354
371
  <script setup>
355
372
  import '@returningai/widget-sdk'
373
+ defineProps({ embedToken: String })
356
374
  </script>
357
375
 
358
376
  <template>
@@ -360,8 +378,7 @@ import '@returningai/widget-sdk'
360
378
  <rai-store-widget
361
379
  community-id="YOUR_COMMUNITY_ID"
362
380
  theme="dark"
363
- :data-email="user.email"
364
- api-url="YOUR_API_URL"
381
+ :embed-token="embedToken"
365
382
  widget-url="YOUR_WIDGET_URL"
366
383
  />
367
384
 
@@ -370,8 +387,7 @@ import '@returningai/widget-sdk'
370
387
  community-id="YOUR_COMMUNITY_ID"
371
388
  channel-id="YOUR_CHANNEL_ID"
372
389
  theme="dark"
373
- :data-email="user.email"
374
- api-url="YOUR_API_URL"
390
+ :embed-token="embedToken"
375
391
  widget-url="YOUR_WIDGET_URL"
376
392
  />
377
393
  </template>
@@ -392,8 +408,7 @@ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
392
408
  <rai-store-widget
393
409
  community-id="YOUR_COMMUNITY_ID"
394
410
  theme="dark"
395
- [attr.data-email]="user.email"
396
- api-url="YOUR_API_URL"
411
+ [attr.embed-token]="embedToken"
397
412
  widget-url="YOUR_WIDGET_URL"
398
413
  ></rai-store-widget>
399
414
 
@@ -402,8 +417,7 @@ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
402
417
  community-id="YOUR_COMMUNITY_ID"
403
418
  channel-id="YOUR_CHANNEL_ID"
404
419
  theme="dark"
405
- [attr.data-email]="user.email"
406
- api-url="YOUR_API_URL"
420
+ [attr.embed-token]="embedToken"
407
421
  widget-url="YOUR_WIDGET_URL"
408
422
  ></rai-channel-widget>
409
423
  ```
@@ -431,7 +445,7 @@ Listen for widget lifecycle events:
431
445
  | `rai-authenticated` | `{}` | Auth succeeded, before widget mounts |
432
446
  | `rai-ready` | `{}` | `WIDGET_READY` received, loader hidden (iframe mode) |
433
447
  | `rai-mounted` | `{}` | Widget bundle mounted successfully (bundle mode) |
434
- | `rai-error` | `{ message }` | Auth failed after all retries |
448
+ | `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. |
435
449
  | `rai-logout` | `{}` | Widget logged out |
436
450
  | `rai-height-change` | `{ height }` | iframe resized (after debounce) |
437
451
 
@@ -445,7 +459,7 @@ const widget = document.querySelector('rai-store-widget')
445
459
  widget.addEventListener('rai-authenticated', () => { /* auth succeeded */ })
446
460
  widget.addEventListener('rai-ready', () => { /* widget loaded and visible (iframe mode) */ })
447
461
  widget.addEventListener('rai-mounted', () => { /* widget mounted (bundle mode) */ })
448
- widget.addEventListener('rai-error', (e) => console.error(e.detail.message))
462
+ widget.addEventListener('rai-error', (e) => console.error(e.detail.status, e.detail.message, e.detail.hint))
449
463
  widget.addEventListener('rai-logout', () => { /* user logged out */ })
450
464
  widget.addEventListener('rai-height-change', (e) => console.log(e.detail.height))
451
465
  ```