npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@returningai/widget-sdk

v1.4.0

Published

Shadow DOM isolated widget SDK for ReturningAI

Downloads

580

Readme

@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 (required in production; localhost is auto-allowed in dev).

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.

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.

Step 2 — Exchange credentials for an embed token on every page load (Node.js example):

// 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,
    // userIdentifiers keys MUST be `data-*` prefixed — they match the
    // `dataAttribute` value of each field configured in your widget's
    // User Identifier Fields (e.g. the dashboard stores "data-email",
    // "data-user-id", not bare "email"/"userId").
    userIdentifiers: {
      'data-user-id': [YourUserObject].id,
      'data-email': [YourUserObject].email,
      // ...any other fields configured for this widget
    },
  }),
})
const { data } = await response.json()
// data.embedToken expires in 15 minutes and carries signed identity claims

Step 3 — Embed (only the short-lived token appears in HTML — no identity attributes):

<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-embed-token="eyJ..."
  widget-url="YOUR_WIDGET_URL"
></script>

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.

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.


Install

CDN (no install needed)

<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"></script>

npm / yarn / pnpm

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 |

All examples below use Access Key Embed (recommended) — identity is signed into data-embed-token server-side per the Quick Start. Swap data-embed-token="eyJ..." for data-* identifier attributes if you want Public Embed instead.

Store widget

<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-embed-token="eyJ..."
  widget-url="YOUR_WIDGET_URL"
></script>

Channel widget

<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-embed-token="eyJ..."
  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 with separate community-id and channel-id attributes.

Social widget

<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-embed-token="eyJ..."
  widget-url="YOUR_WIDGET_URL"
></script>

Milestone widget

<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-embed-token="eyJ..."
  widget-url="YOUR_WIDGET_URL"
></script>

Custom widget

<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-embed-token="eyJ..."
  widget-url="YOUR_WIDGET_URL"
></script>

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.


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 | No | Production widget server | Optional widget auth server override. Defaults from domain-key when provided | | 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 (non-iframe) | | data-domain-key | No | PROD | Environment key: SGTR, STG, or PROD. Used to resolve default environment URLs when overrides are omitted | | data-theme | No | light | light or dark | | data-email | Public Embed only | — | User's email for identification. Access Key Embed: omit — identity is signed into data-embed-token | | data-embed-token | Access Key Embed only | — | Short-lived JWT from your server. Carries signed user identifiers — no data-* identifier attributes needed | | data-locale | No | — | BCP 47 locale tag (e.g. fr-FR) | | data-eager | No | — | Load immediately instead of waiting until visible (presence-based boolean) | | 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 | | data-retry-label | No | Retry | Label text for the retry button on the error screen | | data-v2-api-url | No | Domain-key default | Optional V2 API override for bundle mode. Safe to omit for standard environments |

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 | No | Production widget server | Optional widget auth server override. Defaults from domain-key when provided | | 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 (non-iframe) | | domain-key | No | PROD | Environment key: SGTR, STG, or PROD. Used to resolve default environment URLs when overrides are omitted | | theme | No | light | light or dark | | data-email | Public Embed only | — | User's email for identification. Access Key Embed: omit — identity is signed into embed-token | | embed-token | Access Key Embed only | — | Short-lived JWT from your server. Carries signed user identifiers — no data-* identifier attributes needed | | 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 (presence-based boolean) | | 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 | | retry-label | No | Retry | Label text for the retry button on the error screen | | v2-api-url | No | Domain-key default | Optional V2 API override for bundle mode. Safe to omit for standard environments |

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:

<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

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.

Public Embed: Pass any data-* attribute to identify the current user. Which identifiers are available is configured per community in your dashboard.

<!-- Public Embed — identifiers in 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="[email protected]"
  data-user-id="12345"
  data-account-id="abc"
  widget-url="YOUR_WIDGET_URL"
></script>

In Public Embed mode, 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 (the widget is loaded directly from bundle-url).
  • api-url is optional. The SDK resolves the widget auth server from domain-key when provided, or defaults to production.
  • domain-key is optional. When omitted, the SDK defaults to production (PROD).
  • Environment URL variables such as window.api_url are optional overrides. Standard SGTR, STG, and PROD environments resolve from domain-key.

Both examples below use Access Key Embed and target staging. Omit domain-key for production.

<!-- 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-embed-token="eyJ..."
  data-bundle-url="YOUR_BUNDLE_URL"
  data-domain-key="STG"
  data-eager
></script>

<!-- Custom element approach -->
<rai-store-widget
  community-id="YOUR_COMMUNITY_ID"
  bundle-url="YOUR_BUNDLE_URL"
  domain-key="STG"
  embed-token="eyJ..."
  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 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.)

All examples below use Access Key Embed — pass the short-lived token your server minted into embed-token. For Public Embed, replace embed-token with data-* identifier attributes.

Plain 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"
  embed-token="eyJ..."
  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"
  embed-token="eyJ..."
  widget-url="YOUR_WIDGET_URL"
></rai-channel-widget>

React (TypeScript)

Types are included — no manual JSX declarations needed.

import '@returningai/widget-sdk'

export function StoreEmbed({ embedToken }: { embedToken: string }) {
  return (
    <rai-store-widget
      community-id="YOUR_COMMUNITY_ID"
      theme="dark"
      embed-token={embedToken}
      widget-url="YOUR_WIDGET_URL"
    />
  )
}

export function ChannelEmbed({ embedToken }: { embedToken: string }) {
  return (
    <rai-channel-widget
      community-id="YOUR_COMMUNITY_ID"
      channel-id="YOUR_CHANNEL_ID"
      theme="dark"
      embed-token={embedToken}
      widget-url="YOUR_WIDGET_URL"
    />
  )
}

Vue 3

<script setup>
import '@returningai/widget-sdk'
defineProps({ embedToken: String })
</script>

<template>
  <!-- Store widget -->
  <rai-store-widget
    community-id="YOUR_COMMUNITY_ID"
    theme="dark"
    :embed-token="embedToken"
    widget-url="YOUR_WIDGET_URL"
  />

  <!-- Channel widget -->
  <rai-channel-widget
    community-id="YOUR_COMMUNITY_ID"
    channel-id="YOUR_CHANNEL_ID"
    theme="dark"
    :embed-token="embedToken"
    widget-url="YOUR_WIDGET_URL"
  />
</template>

Angular

// app.module.ts
import '@returningai/widget-sdk'
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'

@NgModule({ schemas: [CUSTOM_ELEMENTS_SCHEMA] })
<!-- Store widget -->
<rai-store-widget
  community-id="YOUR_COMMUNITY_ID"
  theme="dark"
  [attr.embed-token]="embedToken"
  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.embed-token]="embedToken"
  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, 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. | | rai-logout | {} | Widget logged out | | rai-height-change | { height } | iframe resized (after debounce) |

// 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.status, e.detail.message, e.detail.hint))
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:

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 |