@nevermined-io/ui-widgets
v0.5.21
Published
Browser SDK for embedding Nevermined flows (checkout, card enrollment, card management, delegations) into your own website via secure iframes.
Downloads
3,263
Readme
@nevermined-io/ui-widgets
Browser SDK for embedding Nevermined flows (checkout, card enrollment, card management, delegations) into your own website via secure iframes.
Pairs with @nevermined-io/ui-widgets-server, which mints widget sessions server-to-server from your backend and hands the response to this SDK.
Install
npm install @nevermined-io/ui-widgets
# or
pnpm add @nevermined-io/ui-widgetsESM-only. Works in any modern browser bundler (Vite, webpack 5, Rspack, esbuild, etc.).
Quick start
import { NeverminedWidgets } from '@nevermined-io/ui-widgets'
// 1. Get a widget session from your backend (see ui-widgets-server). The
// backend calls Nevermined with your widget key `rawSecret` and forwards
// the response — the secret never reaches the browser.
const session = await fetch('/api/widget-session').then((r) => r.json())
// 2. Initialize the SDK
const nvm = await NeverminedWidgets.initialize({
session,
environment: 'sandbox',
})
// 3. Mount a widget
nvm.checkout.start({
did: 'did:nv:abc...',
container: document.getElementById('checkout')!,
onReady: () => console.log('iframe ready'),
onSuccess: (result) => console.log('purchase complete', result),
onError: (error) => console.error('checkout error', error),
onClose: () => console.log('user closed the iframe'),
})The widget renders inside container as an iframe. The host page never sees Stripe details, the user's NVM API key, or the underlying blockchain transactions — those stay inside the iframe and the Nevermined backend.
Environments
| Value | API base URL | Webapp URL |
| ----------------- | ------------------------------------ | ------------------------ |
| live | https://api.live.nevermined.app | https://nevermined.app |
| sandbox | https://api.sandbox.nevermined.app | https://nevermined.app |
| staging_live | https://api.live.nevermined.dev | https://nevermined.dev |
| staging_sandbox | https://api.sandbox.nevermined.dev | https://nevermined.dev |
| local | http://localhost:3001 | http://localhost:4200 |
local is for developing against a self-hosted stack. Production integrations should use live or sandbox.
API
NeverminedWidgets.initialize(config)
Accepts a widget session minted server-to-server by your backend (typically via @nevermined-io/ui-widgets-server.createWidgetSession) and returns a NeverminedWidgets instance you keep around for the lifetime of the page (or until logout). The SDK does not mint sessions itself; the widget key rawSecret stays on your backend.
const nvm = await NeverminedWidgets.initialize({
session, // WidgetSession returned by your backend
environment: 'live',
})The session refreshes itself automatically in the background. If a refresh fails (revoked widget key, expired session, network outage), the SDK emits 'session-expired':
nvm.on('session-expired', () => {
// Fetch a fresh widget session from your backend and re-initialize.
})nvm.checkout.start(options)
Mounts the checkout iframe so the user can purchase a plan for a given agent DID.
nvm.checkout.start({
did: 'did:nv:...',
planId: '...', // optional: skip plan selection
container: HTMLElement, // optional: defaults to document.body
onBooted: () => void, // iframe DOM mounted, before auth
onReady: () => void, // iframe authenticated and rendered
onSuccess: ({ did, planId, txHash }) => void,
onError: (error: EmbedError) => void,
onClose: () => void, // iframe was dismissed; instance is now terminal
})After onClose the widget instance is terminal — call nvm.checkout.start() again on a fresh instance via nvm.checkout to mount another checkout.
nvm.delegations
Card and delegation management. Three iframe-based flows + two SDK-direct revocations:
// Iframe flows
nvm.delegations.enrollCard({ container, onSuccess, onError, onClose })
nvm.delegations.listCards({ container, onCardAction, onError, onClose })
nvm.delegations.createDelegation({ paymentMethodId, container, onSuccess, onError })
// Direct API calls (no iframe) — use the widget session token under the hood
await nvm.delegations.revokeCard(paymentMethodId)
await nvm.delegations.revokeDelegation(delegationId)listCards emits per-row actions through onCardAction so the host can react (e.g. mount createDelegation for the selected card):
nvm.delegations.listCards({
container,
onCardAction: ({ action, paymentMethodId }) => {
if (action === 'delegate') {
nvm.delegations.createDelegation({ paymentMethodId, container })
}
},
})The two iframe flows and listCards share a single iframe slot — calling any of them dismounts the previous iframe.
nvm.account
const { userId, userWallet } = nvm.accountThe user identity backed by the widget session. userWallet is the user's smart account address.
nvm.destroy()
Tears down any active iframe, stops the session refresh timer, and clears event listeners. Call this when the user logs out or you no longer need any widget on the page.
Errors
WidgetInitError
Thrown by NeverminedWidgets.initialize() when the session is missing or malformed, or the refresh network call fails later.
try {
await NeverminedWidgets.initialize({ session, environment: 'live' })
} catch (err) {
if (err instanceof WidgetInitError) {
// err.code: MISSING_SESSION | INVALID_SESSION | INVALID_ENVIRONMENT
// | NETWORK_ERROR
}
}WidgetSessionExpiredError
Thrown by nvm.getSessionToken() when the cached session has expired and was not refreshed in time.
WidgetApiError
Thrown by revokeCard() and revokeDelegation() on non-2xx responses or network failures. Carries status and the optional apiCode (BCK error code) so consumers can branch on 401/403 without parsing the message.
EmbedError (callback payload)
Errors surfaced through onError callbacks have a normalized shape:
type EmbedError = {
code: 'UNAUTHORIZED' | 'NETWORK' | 'PAYMENT_NOT_CONFIRMED' | 'UNKNOWN'
message: string
status?: number // HTTP status when applicable
apiCode?: string // BCK.* error code when the iframe surfaces one
}postMessage protocol
The SDK and the embedded iframes communicate over window.postMessage with a versioned message envelope. Most consumers never need to deal with this directly, but the types are exported in case you want to inspect frames or build a custom integration:
import {
WidgetMessageType,
parseMessage,
createMessage,
WIDGET_MESSAGE_VERSION,
} from '@nevermined-io/ui-widgets'Message types:
| Type | Direction | When |
| ----------------- | --------------- | --------------------------------------------------------- |
| nvm:booted | iframe → parent | DOM mounted, before the iframe knows the session token |
| nvm:init | parent → iframe | SDK responds to booted with the session token |
| nvm:ready | iframe → parent | Auth validated, iframe rendered |
| nvm:resize | iframe → parent | Iframe content height changed |
| nvm:success | iframe → parent | Terminal success (purchase complete, card enrolled, etc.) |
| nvm:error | iframe → parent | Error (terminal or recoverable; check EmbedError.code) |
| nvm:card-action | iframe → parent | Per-row action inside listCards (e.g. delegate) |
| nvm:close | iframe ↔ parent | Iframe is being dismissed |
All frames carry version: '1'. The SDK rejects frames with mismatched versions to keep upgrades safe.
Security model
- Origin allowlist: every widget key has an
allowedOriginslist. The webapp validates that the host'sparentOriginis on that list before responding to the handshake. A leaked widget key cannot mount widgets on an unrecognized origin. - Session token never in URL: the SDK delivers the session token to the iframe via
postMessageafter anvm:bootedhandshake. The token is not visible in the iframesrc, browser history, or referer headers. - Wildcard origin rejected:
IframeManagerrefuses to construct with'*'. - Sandboxed iframe: the embed routes run in a no-chrome layout that excludes navigation, links to other dashboard areas, and persistent cookies for cross-origin contexts.
License
Apache-2.0 © Nevermined
