@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.
- package/README.md +208 -0
- 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
|
+
"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
|
}
|