@nevermined-io/ui-widgets 0.3.1 → 0.4.1

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.
Files changed (2) hide show
  1. package/README.md +208 -0
  2. package/package.json +5 -2
package/README.md ADDED
@@ -0,0 +1,208 @@
1
+ # @nevermined-io/ui-widgets
2
+
3
+ Browser SDK for embedding Nevermined flows (checkout, card enrollment, card management, delegations) into your own website via secure iframes.
4
+
5
+ Pairs with [`@nevermined-io/ui-widgets-server`](https://www.npmjs.com/package/@nevermined-io/ui-widgets-server), which mints the short-lived init tokens this SDK exchanges for an authenticated widget session.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @nevermined-io/ui-widgets
11
+ # or
12
+ pnpm add @nevermined-io/ui-widgets
13
+ ```
14
+
15
+ ESM-only. Works in any modern browser bundler (Vite, webpack 5, Rspack, esbuild, etc.).
16
+
17
+ ## Quick start
18
+
19
+ ```ts
20
+ import { NeverminedWidgets } from '@nevermined-io/ui-widgets'
21
+
22
+ // 1. Get an init token from your backend (see ui-widgets-server)
23
+ const { initToken } = await fetch('/api/widget-init-token').then(r => r.json())
24
+
25
+ // 2. Initialize the SDK
26
+ const nvm = await NeverminedWidgets.initialize({
27
+ initToken,
28
+ environment: 'sandbox',
29
+ })
30
+
31
+ // 3. Mount a widget
32
+ nvm.checkout.start({
33
+ did: 'did:nv:abc...',
34
+ container: document.getElementById('checkout')!,
35
+ onReady: () => console.log('iframe ready'),
36
+ onSuccess: result => console.log('purchase complete', result),
37
+ onError: error => console.error('checkout error', error),
38
+ onClose: () => console.log('user closed the iframe'),
39
+ })
40
+ ```
41
+
42
+ 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.
43
+
44
+ ## Environments
45
+
46
+ | Value | API base URL | Webapp URL |
47
+ | ----------------- | --------------------------------------- | --------------------------- |
48
+ | `live` | `https://api.live.nevermined.app` | `https://nevermined.app` |
49
+ | `sandbox` | `https://api.sandbox.nevermined.app` | `https://nevermined.app` |
50
+ | `staging_live` | `https://api.live.nevermined.dev` | `https://nevermined.dev` |
51
+ | `staging_sandbox` | `https://api.sandbox.nevermined.dev` | `https://nevermined.dev` |
52
+ | `local` | `http://localhost:3001` | `http://localhost:4200` |
53
+
54
+ `local` is for developing against a self-hosted stack. Production integrations should use `live` or `sandbox`.
55
+
56
+ ## API
57
+
58
+ ### `NeverminedWidgets.initialize(config)`
59
+
60
+ Exchanges the init token for a widget session. Returns a `NeverminedWidgets` instance you keep around for the lifetime of the page (or until logout).
61
+
62
+ ```ts
63
+ const nvm = await NeverminedWidgets.initialize({
64
+ initToken: '...', // from your backend
65
+ environment: 'live',
66
+ })
67
+ ```
68
+
69
+ The session refreshes itself automatically in the background. If a refresh fails (revoked widget key, expired session, network outage), the SDK emits `'session-expired'`:
70
+
71
+ ```ts
72
+ nvm.on('session-expired', () => {
73
+ // Fetch a new init token from your backend and re-initialize.
74
+ })
75
+ ```
76
+
77
+ ### `nvm.checkout.start(options)`
78
+
79
+ Mounts the checkout iframe so the user can purchase a plan for a given agent DID.
80
+
81
+ ```ts
82
+ nvm.checkout.start({
83
+ did: 'did:nv:...',
84
+ planId: '...', // optional: skip plan selection
85
+ container: HTMLElement, // optional: defaults to document.body
86
+ onBooted: () => void, // iframe DOM mounted, before auth
87
+ onReady: () => void, // iframe authenticated and rendered
88
+ onSuccess: ({ did, planId, txHash }) => void,
89
+ onError: (error: EmbedError) => void,
90
+ onClose: () => void, // iframe was dismissed; instance is now terminal
91
+ })
92
+ ```
93
+
94
+ After `onClose` the widget instance is terminal — call `nvm.checkout.start()` again on a fresh instance via `nvm.checkout` to mount another checkout.
95
+
96
+ ### `nvm.delegations`
97
+
98
+ Card and delegation management. Three iframe-based flows + two SDK-direct revocations:
99
+
100
+ ```ts
101
+ // Iframe flows
102
+ nvm.delegations.enrollCard({ container, onSuccess, onError, onClose })
103
+ nvm.delegations.listCards({ container, onCardAction, onError, onClose })
104
+ nvm.delegations.createDelegation({ paymentMethodId, container, onSuccess, onError })
105
+
106
+ // Direct API calls (no iframe) — use the widget session token under the hood
107
+ await nvm.delegations.revokeCard(paymentMethodId)
108
+ await nvm.delegations.revokeDelegation(delegationId)
109
+ ```
110
+
111
+ `listCards` emits per-row actions through `onCardAction` so the host can react (e.g. mount `createDelegation` for the selected card):
112
+
113
+ ```ts
114
+ nvm.delegations.listCards({
115
+ container,
116
+ onCardAction: ({ action, paymentMethodId }) => {
117
+ if (action === 'delegate') {
118
+ nvm.delegations.createDelegation({ paymentMethodId, container })
119
+ }
120
+ },
121
+ })
122
+ ```
123
+
124
+ The two iframe flows and `listCards` share a single iframe slot — calling any of them dismounts the previous iframe.
125
+
126
+ ### `nvm.account`
127
+
128
+ ```ts
129
+ const { userId, userWallet } = nvm.account
130
+ ```
131
+
132
+ The user identity backed by the widget session. `userWallet` is the user's smart account address.
133
+
134
+ ### `nvm.destroy()`
135
+
136
+ 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.
137
+
138
+ ## Errors
139
+
140
+ ### `WidgetInitError`
141
+
142
+ Thrown by `NeverminedWidgets.initialize()` when the init token is rejected, the response is malformed, or the network call fails.
143
+
144
+ ```ts
145
+ try {
146
+ await NeverminedWidgets.initialize({ initToken, environment: 'live' })
147
+ } catch (err) {
148
+ if (err instanceof WidgetInitError) {
149
+ // err.code: MISSING_INIT_TOKEN | INVALID_INIT_TOKEN | INVALID_ENVIRONMENT
150
+ // | INVALID_RESPONSE | NETWORK_ERROR
151
+ }
152
+ }
153
+ ```
154
+
155
+ ### `WidgetSessionExpiredError`
156
+
157
+ Thrown by `nvm.getSessionToken()` when the cached session has expired and was not refreshed in time.
158
+
159
+ ### `WidgetApiError`
160
+
161
+ 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.
162
+
163
+ ### `EmbedError` (callback payload)
164
+
165
+ Errors surfaced through `onError` callbacks have a normalized shape:
166
+
167
+ ```ts
168
+ type EmbedError = {
169
+ code: 'UNAUTHORIZED' | 'NETWORK' | 'PAYMENT_NOT_CONFIRMED' | 'UNKNOWN'
170
+ message: string
171
+ status?: number // HTTP status when applicable
172
+ apiCode?: string // BCK.* error code when the iframe surfaces one
173
+ }
174
+ ```
175
+
176
+ ## postMessage protocol
177
+
178
+ 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:
179
+
180
+ ```ts
181
+ import { WidgetMessageType, parseMessage, createMessage, WIDGET_MESSAGE_VERSION } from '@nevermined-io/ui-widgets'
182
+ ```
183
+
184
+ Message types:
185
+
186
+ | Type | Direction | When |
187
+ | ----------------- | ------------------ | --------------------------------------------------------- |
188
+ | `nvm:booted` | iframe → parent | DOM mounted, before the iframe knows the session token |
189
+ | `nvm:init` | parent → iframe | SDK responds to `booted` with the session token |
190
+ | `nvm:ready` | iframe → parent | Auth validated, iframe rendered |
191
+ | `nvm:resize` | iframe → parent | Iframe content height changed |
192
+ | `nvm:success` | iframe → parent | Terminal success (purchase complete, card enrolled, etc.) |
193
+ | `nvm:error` | iframe → parent | Error (terminal or recoverable; check `EmbedError.code`) |
194
+ | `nvm:card-action` | iframe → parent | Per-row action inside `listCards` (e.g. delegate) |
195
+ | `nvm:close` | iframe ↔ parent | Iframe is being dismissed |
196
+
197
+ All frames carry `version: '1'`. The SDK rejects frames with mismatched versions to keep upgrades safe.
198
+
199
+ ## Security model
200
+
201
+ - **Origin allowlist:** every widget key has an `allowedOrigins` list. The webapp validates that the host's `parentOrigin` is on that list before responding to the handshake. A leaked widget key cannot mount widgets on an unrecognized origin.
202
+ - **Session token never in URL:** the SDK delivers the session token to the iframe via `postMessage` after a `nvm:booted` handshake. The token is not visible in the iframe `src`, browser history, or referer headers.
203
+ - **Wildcard origin rejected:** `IframeManager` refuses to construct with `'*'`.
204
+ - **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.
205
+
206
+ ## License
207
+
208
+ Apache-2.0 © Nevermined
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nevermined-io/ui-widgets",
3
- "version": "0.3.1",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -17,5 +17,8 @@
17
17
  "dist",
18
18
  "!**/*.tsbuildinfo"
19
19
  ],
20
- "devDependencies": {}
20
+ "devDependencies": {
21
+ "vite": "^8.0.5",
22
+ "vite-plugin-dts": "^4.5.4"
23
+ }
21
24
  }