npm package: @returningai/widget-sdk
Description: Shadow DOM isolated widget SDK for ReturningAI
Latest version: 1.2.16

# @returningai/widget-sdk

Embed ReturningAI widgets on any website — React, Vue, Angular, or plain `<script>` tag. No external dependencies.

---

## Quick Start

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.

**Step 1 — Create an access key pair** (one-time, from your dashboard):

Go to **Community Settings > Integrations > SDK Access** and click **Create SDK Key**. Enter a name and optional allowed origins.

The dashboard will display your `accessId` and `accessKey`. The `accessKey` is shown **once only** — save it securely.

Store both values in your server environment variables.

**Step 2 — Exchange credentials for an embed token on every page load** (Node.js example):

```js
// Runs on YOUR SERVER — never in the browser
const response = await fetch('https://YOUR_API_URL/v2/api/widget-access-keys/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    accessId: process.env.RAI_ACCESS_ID,
    accessKey: process.env.RAI_ACCESS_KEY,
  }),
})
const { data } = await response.json()
// data.embedToken expires in 15 minutes
```

**Step 3 — Embed** (only the short-lived token appears in HTML):

```html
<div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
  data-widget-id="YOUR_WIDGET_ID"
  data-widget-type="store"
  data-theme="dark"
  data-email="user@example.com"
  data-embed-token="eyJ..."
  api-url="YOUR_API_URL"
  widget-url="YOUR_WIDGET_URL"
></script>
```

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.

---

## Install

### CDN (no install needed)

```html
<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"></script>
```

### npm / yarn / pnpm

```bash
npm install @returningai/widget-sdk
```

---

## Widget Types

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.

| `data-widget-type` | Description |
|---------------------|-------------|
| `store` | Store / rewards widget |
| `channel` | Channel widget |
| `milestone` | Milestone widget |
| `social` | Social widget |
| `currency-view` | Currency widget |
| `referral-conditions` | Referral widget |
| `custom` | Custom widget |

### Store widget

```html
<div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
  data-widget-id="YOUR_WIDGET_ID"
  data-widget-type="store"
  data-theme="dark"
  data-email="user@example.com"
  api-url="YOUR_API_URL"
  widget-url="YOUR_WIDGET_URL"
></script>
```

### Channel widget

```html
<div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
  data-widget-id="YOUR_WIDGET_ID"
  data-widget-type="channel"
  data-theme="dark"
  data-email="user@example.com"
  api-url="YOUR_API_URL"
  widget-url="YOUR_WIDGET_URL"
></script>
```

> **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.

### Social widget

```html
<div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
  data-widget-id="YOUR_WIDGET_ID"
  data-widget-type="social"
  data-theme="dark"
  data-email="user@example.com"
  api-url="YOUR_API_URL"
  widget-url="YOUR_WIDGET_URL"
></script>
```

### Milestone widget

```html
<div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
  data-widget-id="YOUR_WIDGET_ID"
  data-widget-type="milestone"
  data-theme="dark"
  data-email="user@example.com"
  api-url="YOUR_API_URL"
  widget-url="YOUR_WIDGET_URL"
></script>
```

### Custom widget

```html
<div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
  data-widget-id="YOUR_WIDGET_ID"
  data-widget-type="custom"
  data-theme="dark"
  data-email="user@example.com"
  api-url="YOUR_API_URL"
  widget-url="YOUR_WIDGET_URL"
></script>
```

---

## Attributes

There are two ways to identify your widget:

1. **Script-tag embed:** Use `data-widget-id` (a pre-encoded ID from your dashboard) on the `<script>` tag
2. **Custom element embed:** Use `community-id` (and `channel-id` for channel widgets) on the custom element tag

All attributes can be provided with or without the `data-` prefix — both work.

### Script-tag attributes

| Attribute | Required | Default | Description |
|-----------|----------|---------|-------------|
| `data-widget-id` | **Yes** | — | Your widget ID (from dashboard) |
| `data-widget-type` | **Yes** | `store` | Widget type (see table above) |
| `api-url` | **Yes**\* | — | Auth API base URL (from dashboard) |
| `widget-url` | **Yes**\* | — | Widget URL (from dashboard). \*Not required when using `data-bundle-url` |
| `data-bundle-url` | No | — | URL to the widget IIFE bundle — triggers [bundle mode](#bundle-mode) (non-iframe) |
| `data-domain-key` | Bundle mode | — | Environment key: `SGTR`, `STG`, or `PROD`. Required for bundle mode on external sites |
| `data-theme` | No | `light` | `light` or `dark` |
| `data-email` | Recommended | — | User's email for identification |
| `data-embed-token` | Access Key only | — | Short-lived token from your server |
| `data-locale` | No | — | BCP 47 locale tag (e.g. `fr-FR`) |
| `data-eager` | No | — | Load immediately instead of waiting until visible |
| `data-auto-refresh` | No | `true` | Auto-refresh access token before expiry |
| `data-debug` | No | `false` | Verbose console logging |
| `data-custom-data` | No | — | JSON object forwarded to the widget |

### Custom element attributes

| Attribute | Required | Default | Description |
|-----------|----------|---------|-------------|
| `community-id` | **Yes**\* | — | Your community ID (raw ObjectId from dashboard). \*Custom widgets use `widget-id` instead |
| `channel-id` | **Channel only** | — | Channel ID (raw ObjectId) — required for `<rai-channel-widget>` |
| `api-url` | **Yes** | — | Auth API base URL (from dashboard) |
| `widget-url` | **Yes**\* | — | Widget URL (from dashboard). \*Not required when using `bundle-url` |
| `bundle-url` | No | — | URL to the widget IIFE bundle — triggers [bundle mode](#bundle-mode) (non-iframe) |
| `domain-key` | Bundle mode | — | Environment key: `SGTR`, `STG`, or `PROD`. Required for bundle mode on external sites |
| `theme` | No | `light` | `light` or `dark` |
| `data-email` | Recommended | — | User's email for identification |
| `embed-token` | Access Key only | — | Short-lived token from your server |
| `width` | No | `100%` | CSS width |
| `height` | No | `600px` | CSS height |
| `locale` | No | — | BCP 47 locale tag (e.g. `fr-FR`) |
| `eager` | No | — | Load immediately instead of waiting until visible |
| `auto-refresh` | No | `true` | Auto-refresh access token before expiry |
| `debug` | No | `false` | Verbose console logging |
| `custom-data` | No | — | JSON object forwarded to the widget |

> **`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.

### Container `<div>` (script-tag method only)

The SDK looks for a `<div>` with `id="returning-ai-widget-{YOUR_WIDGET_ID}"`. Size the div to control the widget dimensions:

```html
<div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
```

Custom elements don't need a container div — they size themselves using `width` and `height` attributes (defaults: `100%` and `600px`).

### User Identifier Attributes

Pass any `data-*` attribute to identify the current user. Which identifiers are available is configured per community in your dashboard.

```html
<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
  data-widget-id="YOUR_WIDGET_ID"
  data-widget-type="store"
  data-email="user@example.com"
  data-user-id="12345"
  data-account-id="abc"
  api-url="YOUR_API_URL"
  widget-url="YOUR_WIDGET_URL"
></script>
```

Any `data-*` attribute not reserved by the SDK is forwarded to the auth API as a user identifier.

---

## Bundle Mode

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.

`widget-url` is not needed in bundle mode. `domain-key` is required so the widget resolves the correct environment URLs.

```html
<!-- Script-tag approach -->
<div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:100vh"></div>
<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
  data-widget-id="YOUR_WIDGET_ID"
  data-widget-type="store"
  data-theme="dark"
  data-email="user@example.com"
  data-bundle-url="YOUR_BUNDLE_URL"
  data-domain-key="YOUR_DOMAIN_KEY"
  api-url="YOUR_API_URL"
  data-eager
></script>

<!-- Custom element approach -->
<rai-store-widget
  community-id="YOUR_COMMUNITY_ID"
  api-url="YOUR_API_URL"
  bundle-url="YOUR_BUNDLE_URL"
  domain-key="YOUR_DOMAIN_KEY"
  data-email="user@example.com"
  theme="dark"
  width="100%"
  height="100vh"
  eager
></rai-store-widget>
```

Each widget type has its own IIFE bundle and global name:

| Widget type | Bundle global | Custom element tag |
|-------------|--------------|-------------------|
| `store` | `RaiStoreWidget` | `<rai-store-widget>` |
| `channel` | `RaiChannelWidget` | `<rai-channel-widget>` |
| `social` | `RaiSocialWidget` | `<rai-social-widget>` |
| `milestone` | `RaiMilestoneWidget` | `<rai-milestone-widget>` |
| `currency-view` | `RaiCurrencyWidget` | `<rai-currency-widget>` |
| `referral-conditions` | `RaiReferralWidget` | `<rai-referral-widget>` |
| `custom` | `RaiCustomWidget` | `<rai-custom-widget>` |

The bundle must export a `mount(container, config)` function on its global (e.g. `window.RaiStoreWidget.mount`).

---

## Framework Usage (Custom Elements)

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`.

### Plain HTML

```html
<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"></script>

<!-- Store widget -->
<rai-store-widget
  community-id="YOUR_COMMUNITY_ID"
  theme="dark"
  data-email="user@example.com"
  api-url="YOUR_API_URL"
  widget-url="YOUR_WIDGET_URL"
></rai-store-widget>

<!-- Channel widget — requires both community-id AND channel-id -->
<rai-channel-widget
  community-id="YOUR_COMMUNITY_ID"
  channel-id="YOUR_CHANNEL_ID"
  theme="dark"
  data-email="user@example.com"
  api-url="YOUR_API_URL"
  widget-url="YOUR_WIDGET_URL"
></rai-channel-widget>
```

### React (TypeScript)

Types are included — no manual JSX declarations needed.

```tsx
import '@returningai/widget-sdk'

export function StoreEmbed({ email }: { email: string }) {
  return (
    <rai-store-widget
      community-id="YOUR_COMMUNITY_ID"
      theme="dark"
      data-email={email}
      api-url="YOUR_API_URL"
      widget-url="YOUR_WIDGET_URL"
    />
  )
}

export function ChannelEmbed({ email }: { email: string }) {
  return (
    <rai-channel-widget
      community-id="YOUR_COMMUNITY_ID"
      channel-id="YOUR_CHANNEL_ID"
      theme="dark"
      data-email={email}
      api-url="YOUR_API_URL"
      widget-url="YOUR_WIDGET_URL"
    />
  )
}
```

### Vue 3

```vue
<script setup>
import '@returningai/widget-sdk'
</script>

<template>
  <!-- Store widget -->
  <rai-store-widget
    community-id="YOUR_COMMUNITY_ID"
    theme="dark"
    :data-email="user.email"
    api-url="YOUR_API_URL"
    widget-url="YOUR_WIDGET_URL"
  />

  <!-- Channel widget -->
  <rai-channel-widget
    community-id="YOUR_COMMUNITY_ID"
    channel-id="YOUR_CHANNEL_ID"
    theme="dark"
    :data-email="user.email"
    api-url="YOUR_API_URL"
    widget-url="YOUR_WIDGET_URL"
  />
</template>
```

### Angular

```ts
// app.module.ts
import '@returningai/widget-sdk'
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'

@NgModule({ schemas: [CUSTOM_ELEMENTS_SCHEMA] })
```

```html
<!-- Store widget -->
<rai-store-widget
  community-id="YOUR_COMMUNITY_ID"
  theme="dark"
  [attr.data-email]="user.email"
  api-url="YOUR_API_URL"
  widget-url="YOUR_WIDGET_URL"
></rai-store-widget>

<!-- Channel widget -->
<rai-channel-widget
  community-id="YOUR_COMMUNITY_ID"
  channel-id="YOUR_CHANNEL_ID"
  theme="dark"
  [attr.data-email]="user.email"
  api-url="YOUR_API_URL"
  widget-url="YOUR_WIDGET_URL"
></rai-channel-widget>
```

### Available custom element tags

| Tag | Required attributes | Widget type |
|-----|-------------------|-------------|
| `<rai-store-widget>` | `community-id` | Store |
| `<rai-channel-widget>` | `community-id` + `channel-id` | Channel |
| `<rai-milestone-widget>` | `community-id` | Milestone |
| `<rai-social-widget>` | `community-id` | Social |
| `<rai-currency-widget>` | `community-id` | Currency |
| `<rai-referral-widget>` | `community-id` | Referral |
| `<rai-custom-widget>` | `widget-id` (not `community-id`) | Custom |

---

## DOM Events

Listen for widget lifecycle events:

| Event | `detail` | Fired when |
|-------|----------|-----------|
| `rai-authenticated` | `{}` | Auth succeeded, before widget mounts |
| `rai-ready` | `{}` | `WIDGET_READY` received, loader hidden (iframe mode) |
| `rai-mounted` | `{}` | Widget bundle mounted successfully (bundle mode) |
| `rai-error` | `{ message }` | Auth failed after all retries |
| `rai-logout` | `{}` | Widget logged out |
| `rai-height-change` | `{ height }` | iframe resized (after debounce) |

```js
// Script-tag embed
const widget = document.querySelector('[data-widget-id]')

// Custom element embed
const widget = document.querySelector('rai-store-widget')

widget.addEventListener('rai-authenticated', () => { /* auth succeeded */ })
widget.addEventListener('rai-ready',         () => { /* widget loaded and visible (iframe mode) */ })
widget.addEventListener('rai-mounted',       () => { /* widget mounted (bundle mode) */ })
widget.addEventListener('rai-error',         (e) => console.error(e.detail.message))
widget.addEventListener('rai-logout',        () => { /* user logged out */ })
widget.addEventListener('rai-height-change', (e) => console.log(e.detail.height))
```

---

## Public API

After the SDK loads, `window.ReturningAIWidget` is available:

```js
window.ReturningAIWidget.version           // current SDK version
await window.ReturningAIWidget.reload()    // re-runs auth flow and reloads widget
await window.ReturningAIWidget.logout()    // clears tokens and removes widget
window.ReturningAIWidget.isAuthenticated() // boolean
window.ReturningAIWidget.getTokenInfo()    // token metadata (no token values exposed)
```

---

## Browser Support

Requires Custom Elements v1 + Shadow DOM v1.

| Browser | Minimum version |
|---------|----------------|
| Chrome | 67 |
| Firefox | 63 |
| Safari | 13 |