@nevermined-io/ui-widgets 0.5.9 → 0.5.11
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/dist/index.d.ts +580 -14
- package/dist/index.js +1 -1
- package/dist/index.mjs +30 -17
- package/package.json +1 -1
- package/dist/event-emitter.d.ts +0 -11
- package/dist/event-emitter.d.ts.map +0 -1
- package/dist/iframe-manager.d.ts +0 -44
- package/dist/iframe-manager.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/messages.d.ts +0 -28
- package/dist/messages.d.ts.map +0 -1
- package/dist/nevermined-widgets.d.ts +0 -36
- package/dist/nevermined-widgets.d.ts.map +0 -1
- package/dist/session.d.ts +0 -13
- package/dist/session.d.ts.map +0 -1
- package/dist/types.d.ts +0 -109
- package/dist/types.d.ts.map +0 -1
- package/dist/utils/environment.d.ts +0 -4
- package/dist/utils/environment.d.ts.map +0 -1
- package/dist/utils/errors.d.ts +0 -39
- package/dist/utils/errors.d.ts.map +0 -1
- package/dist/widgets/checkout.d.ts +0 -110
- package/dist/widgets/checkout.d.ts.map +0 -1
- package/dist/widgets/delegations.d.ts +0 -168
- package/dist/widgets/delegations.d.ts.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,580 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Emitted (via the per-widget `onAuthMismatch` callback) when the iframe
|
|
3
|
+
* detects a live host Privy session whose email does not match the email
|
|
4
|
+
* the widget session was minted for. The iframe renders a two-button prompt
|
|
5
|
+
* (continue as the host identity, or switch to the widget-session identity
|
|
6
|
+
* by signing out of Privy inside the iframe) and the host page is informed
|
|
7
|
+
* so it can mirror that affordance on its own UI. The widget is effectively
|
|
8
|
+
* halted until either button is clicked or the host re-mints a widget
|
|
9
|
+
* session via the action below.
|
|
10
|
+
*/
|
|
11
|
+
export declare interface AuthMismatchDetail {
|
|
12
|
+
/** Email the widget session was minted for (normalized — trimmed, lower-cased). */
|
|
13
|
+
expectedEmail: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Emitted (via the per-widget `onAuthSwitchRequest` callback) when the user
|
|
18
|
+
* picks "Continue as <hostEmail>" on the mismatch prompt. The host page is
|
|
19
|
+
* expected to re-mint a widget session bound to `requestedEmail` and reopen
|
|
20
|
+
* the widget; the iframe stays on the prompt until that happens.
|
|
21
|
+
*
|
|
22
|
+
* **Security note for integrators:** the SDK posts this event to the host
|
|
23
|
+
* via `window.postMessage`, but the host's `window.addEventListener('message',
|
|
24
|
+
* ...)` listens to ALL frames on the page. Validate `event.origin === <your
|
|
25
|
+
* widget host>` (e.g. `https://app.nevermined.io`) before re-minting a
|
|
26
|
+
* session — otherwise a malicious sub-frame could spoof this event and
|
|
27
|
+
* trick your backend into minting a session for an attacker-supplied email.
|
|
28
|
+
*/
|
|
29
|
+
export declare interface AuthSwitchRequestDetail {
|
|
30
|
+
/** Email the user wants the widget to operate under (their live Privy identity). */
|
|
31
|
+
requestedEmail: string;
|
|
32
|
+
/** Email the original widget session was minted for, for reference / telemetry. */
|
|
33
|
+
expectedEmail: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export declare interface CardAction {
|
|
37
|
+
/**
|
|
38
|
+
* `delegate` — the user asked to create a delegation for this card (host
|
|
39
|
+
* should mount `createDelegation`). `revoked` — the user removed this card
|
|
40
|
+
* inline; the list iframe stays open so this is an intra-flow event, not
|
|
41
|
+
* flow completion (which is why it is NOT an `nvm:success`). See #1411.
|
|
42
|
+
*/
|
|
43
|
+
action: 'delegate' | 'revoked';
|
|
44
|
+
paymentMethodId: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export declare interface CheckoutOptions {
|
|
48
|
+
did: string;
|
|
49
|
+
planId?: string;
|
|
50
|
+
container?: HTMLElement;
|
|
51
|
+
/**
|
|
52
|
+
* Fixed iframe width in pixels. Clamped by the SDK to a supported range
|
|
53
|
+
* (see `INLINE_MIN_WIDTH` / `INLINE_MAX_WIDTH` / `INLINE_DEFAULT_WIDTH`
|
|
54
|
+
* in `iframe-manager`). Ignored in fullscreen overlay mode (no container).
|
|
55
|
+
*/
|
|
56
|
+
width?: number;
|
|
57
|
+
/**
|
|
58
|
+
* Fixed iframe height in pixels. Clamped by the SDK to a supported range
|
|
59
|
+
* (see `INLINE_MIN_HEIGHT` / `INLINE_MAX_HEIGHT` / `INLINE_DEFAULT_HEIGHT`
|
|
60
|
+
* in `iframe-manager`). Ignored in fullscreen overlay mode.
|
|
61
|
+
*/
|
|
62
|
+
height?: number;
|
|
63
|
+
onBooted?: () => void;
|
|
64
|
+
onReady?: () => void;
|
|
65
|
+
/**
|
|
66
|
+
* Fires when the iframe posts `nvm:success`. The widget stays mounted in
|
|
67
|
+
* its success state — the integrator dismisses it via `event.handle.close()`
|
|
68
|
+
* when their own post-success flow is ready. There is no auto-dismiss.
|
|
69
|
+
*/
|
|
70
|
+
onSuccess?: (event: WidgetSuccessEvent<CheckoutResult>) => void;
|
|
71
|
+
onError?: (error: EmbedError) => void;
|
|
72
|
+
/**
|
|
73
|
+
* Fires when the iframe detects that the live host Privy session belongs
|
|
74
|
+
* to a different account than the email the widget session was minted for.
|
|
75
|
+
* The iframe stays mounted on a two-button prompt; the host can mirror that
|
|
76
|
+
* affordance on its own UI. See `AuthMismatchDetail`.
|
|
77
|
+
*/
|
|
78
|
+
onAuthMismatch?: (detail: AuthMismatchDetail) => void;
|
|
79
|
+
/**
|
|
80
|
+
* Fires when, on the mismatch prompt, the user picks "Continue as
|
|
81
|
+
* <hostEmail>". The host should re-mint a widget session bound to
|
|
82
|
+
* `requestedEmail` and reopen the widget. The iframe stays on the prompt
|
|
83
|
+
* until then. See `AuthSwitchRequestDetail`.
|
|
84
|
+
*/
|
|
85
|
+
onAuthSwitchRequest?: (detail: AuthSwitchRequestDetail) => void;
|
|
86
|
+
onClose?: () => void;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export declare interface CheckoutResult {
|
|
90
|
+
did: string;
|
|
91
|
+
planId?: string;
|
|
92
|
+
txHash?: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Checkout widget.
|
|
97
|
+
*
|
|
98
|
+
* Lifecycle note for SDK consumers: when the iframe sends `nvm:close` the
|
|
99
|
+
* widget instance auto-calls `destroy()` and becomes terminal — any
|
|
100
|
+
* subsequent `start()` call throws. If the host page needs to re-show the
|
|
101
|
+
* checkout after a close, construct a new `CheckoutWidget` (typically via
|
|
102
|
+
* `nvm.checkout`) and call `start()` on the fresh instance.
|
|
103
|
+
*/
|
|
104
|
+
export declare class CheckoutWidget {
|
|
105
|
+
private readonly session;
|
|
106
|
+
private readonly webappBase;
|
|
107
|
+
private manager;
|
|
108
|
+
private destroyed;
|
|
109
|
+
constructor(session: SessionManager, webappBase: string);
|
|
110
|
+
/**
|
|
111
|
+
* Mounts the checkout iframe into `options.container`.
|
|
112
|
+
* Throws if called after `destroy()` (including the implicit destroy on
|
|
113
|
+
* `nvm:close` — see class JSDoc).
|
|
114
|
+
*/
|
|
115
|
+
start(options: CheckoutOptions): void;
|
|
116
|
+
/**
|
|
117
|
+
* Mounts a plan-only checkout iframe (no agent DID required).
|
|
118
|
+
* Throws if called after `destroy()` (including the implicit destroy on
|
|
119
|
+
* `nvm:close` — see class JSDoc).
|
|
120
|
+
*/
|
|
121
|
+
startPlan(options: PlanCheckoutOptions): void;
|
|
122
|
+
destroy(): void;
|
|
123
|
+
/**
|
|
124
|
+
* Shared close path for `handle.close()` (integrator-driven) and
|
|
125
|
+
* `nvm:close` (iframe-driven). Idempotent — multiple calls collapse to a
|
|
126
|
+
* single `onClose` + destroy, which matters because `handle.close()`
|
|
127
|
+
* could race with a `nvm:close` from the iframe if the integrator's
|
|
128
|
+
* post-success UI happens to mount instantly.
|
|
129
|
+
*
|
|
130
|
+
* `manager` is the IframeManager this close was bound to when the handle
|
|
131
|
+
* was minted. A stale handle from a previous mount (the instance was
|
|
132
|
+
* remounted via a second `start()` without an intervening close) must NOT
|
|
133
|
+
* tear down the now-live iframe, so we bail once it no longer owns
|
|
134
|
+
* `this.manager`.
|
|
135
|
+
*
|
|
136
|
+
* Ordering is load-bearing: `destroy()` runs before `onClose()` so a
|
|
137
|
+
* throwing `onClose` cannot leak a second close — `destroyed` is already
|
|
138
|
+
* true by the time the callback runs.
|
|
139
|
+
*/
|
|
140
|
+
private closeFromIframe;
|
|
141
|
+
private buildSuccessHandle;
|
|
142
|
+
private mount;
|
|
143
|
+
private handleMessage;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export declare interface CreateDelegationOptions {
|
|
147
|
+
paymentMethodId: string;
|
|
148
|
+
container?: HTMLElement;
|
|
149
|
+
/** See `CheckoutOptions.width`. */
|
|
150
|
+
width?: number;
|
|
151
|
+
/** See `CheckoutOptions.height`. */
|
|
152
|
+
height?: number;
|
|
153
|
+
onBooted?: () => void;
|
|
154
|
+
onReady?: () => void;
|
|
155
|
+
/** See `CheckoutOptions.onSuccess`. */
|
|
156
|
+
onSuccess?: (event: WidgetSuccessEvent<CreateDelegationResult>) => void;
|
|
157
|
+
onError?: (error: EmbedError) => void;
|
|
158
|
+
/** See `CheckoutOptions.onAuthMismatch`. */
|
|
159
|
+
onAuthMismatch?: (detail: AuthMismatchDetail) => void;
|
|
160
|
+
/** See `CheckoutOptions.onAuthSwitchRequest`. */
|
|
161
|
+
onAuthSwitchRequest?: (detail: AuthSwitchRequestDetail) => void;
|
|
162
|
+
onClose?: () => void;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export declare interface CreateDelegationResult {
|
|
166
|
+
delegationId: string;
|
|
167
|
+
paymentMethodId: string;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export declare function createMessage<T>(type: WidgetMessageType, payload?: T): WidgetMessage<T>;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Card and delegation management widget.
|
|
174
|
+
*
|
|
175
|
+
* Surfaces three iframe-based flows (enrollment, listing, delegation creation)
|
|
176
|
+
* and two SDK-direct revocations. The three iframe flows share the single
|
|
177
|
+
* `manager` slot — calling any of them destroys the previously-mounted iframe
|
|
178
|
+
* (same semantics as calling `enrollCard()` twice). Once any iframe is closed
|
|
179
|
+
* by the user (`nvm:close`), the instance is implicitly destroyed and any
|
|
180
|
+
* subsequent iframe call throws. Construct a fresh widget (via
|
|
181
|
+
* `nvm.delegations`) to mount another flow after a close.
|
|
182
|
+
*
|
|
183
|
+
* The two `revoke*` methods do NOT use an iframe — they hit the
|
|
184
|
+
* `/api/v1/widgets/...` endpoints directly with the widget session token.
|
|
185
|
+
* They exist because the SDK consumer on the host page only holds the widget
|
|
186
|
+
* session token; the apiKeyHash that gates the standard delegation/payment
|
|
187
|
+
* endpoints never leaves the embedded iframe.
|
|
188
|
+
*/
|
|
189
|
+
export declare class DelegationsWidget {
|
|
190
|
+
private readonly session;
|
|
191
|
+
private readonly webappBase;
|
|
192
|
+
private readonly apiBase;
|
|
193
|
+
private manager;
|
|
194
|
+
private destroyed;
|
|
195
|
+
constructor(session: SessionManager, webappBase: string, apiBase: string);
|
|
196
|
+
/**
|
|
197
|
+
* Mounts the enrollment iframe at `/embed/cards/enroll`.
|
|
198
|
+
* Throws if called after `destroy()` (including the implicit destroy on
|
|
199
|
+
* `nvm:close` — see class JSDoc).
|
|
200
|
+
*/
|
|
201
|
+
enrollCard(options: EnrollCardOptions): void;
|
|
202
|
+
/**
|
|
203
|
+
* Mounts the cards-list iframe at `/embed/cards/list`. Per-row actions
|
|
204
|
+
* ("Create Delegation" → `'delegate'`, "Remove Card" → `'revoked'`) are
|
|
205
|
+
* forwarded via `onCardAction` so the host can react (mount a follow-up
|
|
206
|
+
* widget, refresh its own list, etc.). See `CardAction`.
|
|
207
|
+
*/
|
|
208
|
+
listCards(options: ListCardsOptions): void;
|
|
209
|
+
/**
|
|
210
|
+
* Mounts the delegation creation iframe at `/embed/cards/delegate` for a
|
|
211
|
+
* specific payment method. The `paymentMethodId` is required and is passed
|
|
212
|
+
* as a search param so the embed route can scope the form to that card.
|
|
213
|
+
*/
|
|
214
|
+
createDelegation(options: CreateDelegationOptions): void;
|
|
215
|
+
/**
|
|
216
|
+
* Revoke (detach) a payment method via the widget-prefixed API endpoint.
|
|
217
|
+
* Resolves on 2xx; throws `WidgetApiError` on any other response or
|
|
218
|
+
* network failure.
|
|
219
|
+
*/
|
|
220
|
+
revokeCard(paymentMethodId: string): Promise<void>;
|
|
221
|
+
/**
|
|
222
|
+
* Revoke a delegation via the widget-prefixed API endpoint.
|
|
223
|
+
* Resolves on 2xx; throws `WidgetApiError` on any other response or
|
|
224
|
+
* network failure.
|
|
225
|
+
*/
|
|
226
|
+
revokeDelegation(delegationId: string): Promise<void>;
|
|
227
|
+
destroy(): void;
|
|
228
|
+
private assertAlive;
|
|
229
|
+
private mountIframe;
|
|
230
|
+
private postInitOnBooted;
|
|
231
|
+
private handleEnrollMessage;
|
|
232
|
+
private handleListMessage;
|
|
233
|
+
private handleCreateDelegationMessage;
|
|
234
|
+
/**
|
|
235
|
+
* #1668: shared close path for `handle.close()` (integrator-driven) and
|
|
236
|
+
* `nvm:close` (iframe-driven). Idempotent so a race between the
|
|
237
|
+
* integrator dismissing the widget and the iframe emitting CLOSE
|
|
238
|
+
* collapses to a single `onClose` + destroy. Generic over the three
|
|
239
|
+
* options shapes — only `onClose` is referenced.
|
|
240
|
+
*
|
|
241
|
+
* `manager` is the IframeManager this close was bound to when the handle
|
|
242
|
+
* was minted. The three iframe flows share the single `manager` slot, so a
|
|
243
|
+
* stale handle from an earlier flow (e.g. an uncalled `enrollCard` success
|
|
244
|
+
* handle held past a later `createDelegation`) must NOT tear down the
|
|
245
|
+
* now-live iframe — we bail once it no longer owns `this.manager`.
|
|
246
|
+
*
|
|
247
|
+
* Ordering is load-bearing: `destroy()` runs before `onClose()` so a
|
|
248
|
+
* throwing `onClose` cannot leak a second close — `destroyed` is already
|
|
249
|
+
* true by the time the callback runs.
|
|
250
|
+
*/
|
|
251
|
+
private closeFromIframe;
|
|
252
|
+
private buildSuccessHandle;
|
|
253
|
+
private deleteWithSession;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export declare interface EmbedError {
|
|
257
|
+
code: 'UNAUTHORIZED' | 'NETWORK' | 'PAYMENT_NOT_CONFIRMED' | 'UNKNOWN';
|
|
258
|
+
/**
|
|
259
|
+
* Raw, host-facing message. Carries the underlying detail (e.g. the backend
|
|
260
|
+
* `NVMException.message`) and is what the SDK forwards over the `nvm:error`
|
|
261
|
+
* postMessage for the integrator to log. May contain operational/technical
|
|
262
|
+
* text, so it is NOT safe to render in an end-user surface verbatim.
|
|
263
|
+
*/
|
|
264
|
+
message: string;
|
|
265
|
+
/**
|
|
266
|
+
* Safe, user-facing message. Generic per-`code` copy with no operational
|
|
267
|
+
* detail — render this (falling back to `message`) in any UI shown to the
|
|
268
|
+
* end user, such as the widgets' terminal error panels. Optional and
|
|
269
|
+
* additive: existing host integrations that only read `message` are
|
|
270
|
+
* unaffected.
|
|
271
|
+
*/
|
|
272
|
+
userMessage?: string;
|
|
273
|
+
status?: number;
|
|
274
|
+
apiCode?: string;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export declare interface EnrollCardOptions {
|
|
278
|
+
container?: HTMLElement;
|
|
279
|
+
/** See `CheckoutOptions.width`. */
|
|
280
|
+
width?: number;
|
|
281
|
+
/** See `CheckoutOptions.height`. */
|
|
282
|
+
height?: number;
|
|
283
|
+
/** See `EnrollCardProvider`. Defaults to `'stripe'`. */
|
|
284
|
+
provider?: EnrollCardProvider;
|
|
285
|
+
onBooted?: () => void;
|
|
286
|
+
onReady?: () => void;
|
|
287
|
+
/** See `CheckoutOptions.onSuccess`. */
|
|
288
|
+
onSuccess?: (event: WidgetSuccessEvent<EnrollCardResult>) => void;
|
|
289
|
+
onError?: (error: EmbedError) => void;
|
|
290
|
+
/** See `CheckoutOptions.onAuthMismatch`. */
|
|
291
|
+
onAuthMismatch?: (detail: AuthMismatchDetail) => void;
|
|
292
|
+
/** See `CheckoutOptions.onAuthSwitchRequest`. */
|
|
293
|
+
onAuthSwitchRequest?: (detail: AuthSwitchRequestDetail) => void;
|
|
294
|
+
onClose?: () => void;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* #1668 sub-task 2: which tokenization flow renders inside the embedded
|
|
299
|
+
* enrol-card iframe.
|
|
300
|
+
*
|
|
301
|
+
* - `'stripe'` (default): Stripe Elements + SetupIntent. The existing flow.
|
|
302
|
+
* - `'braintree'`: Braintree Drop-in (PayPal vault).
|
|
303
|
+
* - `'visa'`: Visa Agentic Tokens via VGS Collect → CMP. **Requires an
|
|
304
|
+
* HTTPS parent page** — the Visa VTS iframe enforces
|
|
305
|
+
* `frame-ancestors 'self' https:` so a non-HTTPS host (e.g. plain
|
|
306
|
+
* `http://localhost`) cannot embed this provider.
|
|
307
|
+
*/
|
|
308
|
+
export declare type EnrollCardProvider = 'stripe' | 'braintree' | 'visa';
|
|
309
|
+
|
|
310
|
+
export declare interface EnrollCardResult {
|
|
311
|
+
paymentMethodId: string;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export declare type Environment = (typeof ENVIRONMENTS)[number];
|
|
315
|
+
|
|
316
|
+
declare const ENVIRONMENTS: readonly ["local", "sandbox", "live", "staging_sandbox", "staging_live"];
|
|
317
|
+
|
|
318
|
+
declare type EventHandler<T> = (payload: T) => void;
|
|
319
|
+
|
|
320
|
+
export declare class IframeManager {
|
|
321
|
+
private expectedOrigin;
|
|
322
|
+
private iframe;
|
|
323
|
+
private messageHandlers;
|
|
324
|
+
private protocolErrorHandlers;
|
|
325
|
+
private messageListener;
|
|
326
|
+
private destroyed;
|
|
327
|
+
constructor(expectedOrigin: string);
|
|
328
|
+
create(url: string, options?: IframeOptions): HTMLIFrameElement;
|
|
329
|
+
destroy(): void;
|
|
330
|
+
postMessage(msg: WidgetMessage): void;
|
|
331
|
+
onMessage(handler: (msg: WidgetMessage) => void): () => void;
|
|
332
|
+
onProtocolError(handler: (reason: string) => void): () => void;
|
|
333
|
+
private ensureListener;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export declare interface IframeOptions {
|
|
337
|
+
container?: HTMLElement;
|
|
338
|
+
style?: Partial<CSSStyleDeclaration>;
|
|
339
|
+
/**
|
|
340
|
+
* Fixed CSS width for inline (container) mode. #1668: the widget renders
|
|
341
|
+
* as a fixed-size box, like a Privy UI component, so the integrator can
|
|
342
|
+
* place it predictably on their page. Clamped to
|
|
343
|
+
* `[INLINE_MIN_WIDTH, INLINE_MAX_WIDTH]`. Default: `INLINE_DEFAULT_WIDTH`
|
|
344
|
+
* — the constants are the single source of truth, so future re-tunings
|
|
345
|
+
* don't require touching this JSDoc. Ignored in fullscreen overlay mode.
|
|
346
|
+
*/
|
|
347
|
+
width?: number;
|
|
348
|
+
/**
|
|
349
|
+
* Fixed CSS height for inline mode. Clamped to
|
|
350
|
+
* `[INLINE_MIN_HEIGHT, INLINE_MAX_HEIGHT]`. Default: `INLINE_DEFAULT_HEIGHT`.
|
|
351
|
+
* The embed content uses responsive CSS to adapt to the dimensions the
|
|
352
|
+
* integrator picks — within the clamped range — without distorting (no
|
|
353
|
+
* CSS transform scale). Ignored in fullscreen overlay mode.
|
|
354
|
+
*/
|
|
355
|
+
height?: number;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export declare interface ListCardsOptions {
|
|
359
|
+
container?: HTMLElement;
|
|
360
|
+
/** See `CheckoutOptions.width`. */
|
|
361
|
+
width?: number;
|
|
362
|
+
/** See `CheckoutOptions.height`. */
|
|
363
|
+
height?: number;
|
|
364
|
+
onBooted?: () => void;
|
|
365
|
+
onReady?: () => void;
|
|
366
|
+
onCardAction?: (action: CardAction) => void;
|
|
367
|
+
onError?: (error: EmbedError) => void;
|
|
368
|
+
/** See `CheckoutOptions.onAuthMismatch`. */
|
|
369
|
+
onAuthMismatch?: (detail: AuthMismatchDetail) => void;
|
|
370
|
+
/** See `CheckoutOptions.onAuthSwitchRequest`. */
|
|
371
|
+
onAuthSwitchRequest?: (detail: AuthSwitchRequestDetail) => void;
|
|
372
|
+
onClose?: () => void;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export declare class NeverminedWidgets {
|
|
376
|
+
private readonly sessionManager;
|
|
377
|
+
private readonly _account;
|
|
378
|
+
private readonly environment;
|
|
379
|
+
private _checkout;
|
|
380
|
+
private _delegations;
|
|
381
|
+
private readonly events;
|
|
382
|
+
private constructor();
|
|
383
|
+
static initialize(config: WidgetConfig): Promise<NeverminedWidgets>;
|
|
384
|
+
private refreshSession;
|
|
385
|
+
on<K extends keyof NeverminedWidgetsEvents>(event: K, handler: (payload: NeverminedWidgetsEvents[K]) => void): this;
|
|
386
|
+
off<K extends keyof NeverminedWidgetsEvents>(event: K, handler: (payload: NeverminedWidgetsEvents[K]) => void): this;
|
|
387
|
+
get account(): WidgetAccount;
|
|
388
|
+
get hasValidSession(): boolean;
|
|
389
|
+
getSessionToken(): string;
|
|
390
|
+
get checkout(): CheckoutWidget;
|
|
391
|
+
get delegations(): DelegationsWidget;
|
|
392
|
+
/**
|
|
393
|
+
* Resets the parent widget container: tears down any live child widget (e.g.
|
|
394
|
+
* the checkout widget) and clears the internal cache. This is a reset, not a
|
|
395
|
+
* terminal state — accessing `widget.checkout` after `destroy()` lazily
|
|
396
|
+
* creates a fresh `CheckoutWidget` instance. If you need a terminal "this
|
|
397
|
+
* widget can no longer be used" semantics, call `destroy()` on the child
|
|
398
|
+
* widget directly (e.g. `widget.checkout.destroy()`), which flips its
|
|
399
|
+
* internal `destroyed` flag and makes subsequent `start()` calls throw.
|
|
400
|
+
*/
|
|
401
|
+
destroy(): void;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
declare interface NeverminedWidgetsEvents {
|
|
405
|
+
'session-expired': void;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export declare function parseMessage(data: unknown): ParseResult;
|
|
409
|
+
|
|
410
|
+
export declare type ParseResult<T = unknown> = {
|
|
411
|
+
ok: true;
|
|
412
|
+
message: WidgetMessage<T>;
|
|
413
|
+
} | {
|
|
414
|
+
ok: false;
|
|
415
|
+
reason: string;
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
declare interface PlanCheckoutOptions {
|
|
419
|
+
planId: string;
|
|
420
|
+
container?: HTMLElement;
|
|
421
|
+
width?: number;
|
|
422
|
+
height?: number;
|
|
423
|
+
onBooted?: () => void;
|
|
424
|
+
onReady?: () => void;
|
|
425
|
+
onSuccess?: (event: WidgetSuccessEvent<PlanCheckoutResult>) => void;
|
|
426
|
+
onError?: (error: EmbedError) => void;
|
|
427
|
+
onAuthMismatch?: (detail: AuthMismatchDetail) => void;
|
|
428
|
+
onAuthSwitchRequest?: (detail: AuthSwitchRequestDetail) => void;
|
|
429
|
+
onClose?: () => void;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
declare interface PlanCheckoutResult {
|
|
433
|
+
planId: string;
|
|
434
|
+
txHash?: string;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export declare class SessionManager {
|
|
438
|
+
private session;
|
|
439
|
+
private expiresMs;
|
|
440
|
+
private timer;
|
|
441
|
+
constructor(session: WidgetSession);
|
|
442
|
+
isValid(): boolean;
|
|
443
|
+
getToken(): string;
|
|
444
|
+
getSession(): WidgetSession;
|
|
445
|
+
startAutoRefresh(refreshFn: () => Promise<WidgetSession>, onExpired: () => void): void;
|
|
446
|
+
stopAutoRefresh(): void;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export declare class TypedEventEmitter<Events extends object> {
|
|
450
|
+
private listeners;
|
|
451
|
+
on<K extends keyof Events>(event: K, handler: EventHandler<Events[K]>): this;
|
|
452
|
+
off<K extends keyof Events>(event: K, handler: EventHandler<Events[K]>): this;
|
|
453
|
+
once<K extends keyof Events>(event: K, handler: EventHandler<Events[K]>): this;
|
|
454
|
+
removeAllListeners(event?: keyof Events): this;
|
|
455
|
+
emit<K extends keyof Events>(event: K, payload: Events[K]): void;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
declare const WIDGET_ERRORS: {
|
|
459
|
+
readonly MISSING_SESSION: {
|
|
460
|
+
readonly numericCode: "WDG.0001";
|
|
461
|
+
readonly message: "session is required — pass the WidgetSession returned by createWidgetSession on the backend";
|
|
462
|
+
};
|
|
463
|
+
readonly INVALID_ENVIRONMENT: {
|
|
464
|
+
readonly numericCode: "WDG.0002";
|
|
465
|
+
readonly message: "environment must be one of: sandbox, live, staging_sandbox, staging_live, local";
|
|
466
|
+
};
|
|
467
|
+
readonly INVALID_SESSION: {
|
|
468
|
+
readonly numericCode: "WDG.0003";
|
|
469
|
+
readonly message: "session is missing required fields";
|
|
470
|
+
};
|
|
471
|
+
readonly SESSION_EXPIRED: {
|
|
472
|
+
readonly numericCode: "WDG.0006";
|
|
473
|
+
readonly message: "Widget session has expired";
|
|
474
|
+
};
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
export declare const WIDGET_MESSAGE_VERSION: "1";
|
|
478
|
+
|
|
479
|
+
export declare interface WidgetAccount {
|
|
480
|
+
userId: string;
|
|
481
|
+
userWallet: `0x${string}`;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Thrown by SDK methods that hit the API directly (no iframe), e.g.
|
|
486
|
+
* `delegations.revokeCard()` / `delegations.revokeDelegation()`. Carries the
|
|
487
|
+
* HTTP status and the optional BCK error code so consumers can branch on
|
|
488
|
+
* 401/403/etc. without parsing the message.
|
|
489
|
+
*/
|
|
490
|
+
export declare class WidgetApiError extends Error {
|
|
491
|
+
readonly status?: number | undefined;
|
|
492
|
+
readonly apiCode?: string | undefined;
|
|
493
|
+
constructor(message: string, status?: number | undefined, apiCode?: string | undefined);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
export declare interface WidgetConfig {
|
|
497
|
+
/**
|
|
498
|
+
* Widget session minted server-to-server by the integrator backend
|
|
499
|
+
* (typically via `@nevermined-io/ui-widgets-server.createWidgetSession`).
|
|
500
|
+
* Forward the response object verbatim — the SDK never holds the widget
|
|
501
|
+
* key `rawSecret`.
|
|
502
|
+
*/
|
|
503
|
+
session: WidgetSession;
|
|
504
|
+
environment: Environment;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
declare type WidgetErrorCode = keyof typeof WIDGET_ERRORS;
|
|
508
|
+
|
|
509
|
+
export declare class WidgetInitError extends Error {
|
|
510
|
+
readonly code: WidgetErrorCode;
|
|
511
|
+
constructor(code: WidgetErrorCode, message?: string, cause?: unknown);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
export declare interface WidgetMessage<T = unknown> {
|
|
515
|
+
type: WidgetMessageType;
|
|
516
|
+
version: '1';
|
|
517
|
+
payload?: T;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
export declare enum WidgetMessageType {
|
|
521
|
+
INIT = "nvm:init",
|
|
522
|
+
CLOSE = "nvm:close",
|
|
523
|
+
BOOTED = "nvm:booted",
|
|
524
|
+
READY = "nvm:ready",
|
|
525
|
+
RESIZE = "nvm:resize",
|
|
526
|
+
SUCCESS = "nvm:success",
|
|
527
|
+
ERROR = "nvm:error",
|
|
528
|
+
CARD_ACTION = "nvm:card-action",
|
|
529
|
+
AUTH_MISMATCH = "nvm:auth-mismatch",
|
|
530
|
+
AUTH_SWITCH_REQUEST = "nvm:auth-switch-request"
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
export declare interface WidgetSession {
|
|
534
|
+
sessionToken: string;
|
|
535
|
+
userId: string;
|
|
536
|
+
userWallet: `0x${string}`;
|
|
537
|
+
/**
|
|
538
|
+
* Hash of the NVM API key bound to this widget session user. Returned by
|
|
539
|
+
* `POST /api/v1/widgets/session` so host pages that want to call user-scoped
|
|
540
|
+
* endpoints directly (outside the iframe) have the bearer token to do so.
|
|
541
|
+
* The embedded iframe flow consumes it via the session JWT claims rather
|
|
542
|
+
* than this field.
|
|
543
|
+
*/
|
|
544
|
+
apiKeyHash: string;
|
|
545
|
+
expiresAt: string;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
export declare class WidgetSessionExpiredError extends Error {
|
|
549
|
+
readonly code: "SESSION_EXPIRED";
|
|
550
|
+
constructor();
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Payload shape passed to every widget's `onSuccess` callback. `result`
|
|
555
|
+
* is the per-widget data (paymentMethodId, delegationId, etc.); `handle`
|
|
556
|
+
* lets the integrator dismiss the widget on their own schedule.
|
|
557
|
+
*/
|
|
558
|
+
export declare interface WidgetSuccessEvent<T> {
|
|
559
|
+
result: T;
|
|
560
|
+
handle: WidgetSuccessHandle;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Control surface passed to every widget's `onSuccess` callback (#1668
|
|
565
|
+
* sub-tasks 3 + 4). The iframe stays mounted indefinitely after a
|
|
566
|
+
* successful action so the integrator can show their own post-action UI
|
|
567
|
+
* (toast, next-step prompt, etc.) while the success state remains visible
|
|
568
|
+
* inside the widget. The integrator dismisses the widget by calling
|
|
569
|
+
* `handle.close()` — which destroys the iframe and invokes `onClose` —
|
|
570
|
+
* whenever their own flow is ready.
|
|
571
|
+
*/
|
|
572
|
+
export declare interface WidgetSuccessHandle {
|
|
573
|
+
/**
|
|
574
|
+
* Destroys the iframe and invokes the widget's `onClose` callback.
|
|
575
|
+
* Idempotent: calling close on an already-closed widget is a no-op.
|
|
576
|
+
*/
|
|
577
|
+
close(): void;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
export { }
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.NeverminedWidgets={}))})(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=[`local`,`sandbox`,`live`,`staging_sandbox`,`staging_live`],n={local:`http://localhost:3001`,sandbox:`https://api.sandbox.nevermined.app`,live:`https://api.live.nevermined.app`,staging_sandbox:`https://api.sandbox.nevermined.dev`,staging_live:`https://api.live.nevermined.dev`},r={local:`http://localhost:4200`,sandbox:`https://nevermined.app`,live:`https://nevermined.app`,staging_sandbox:`https://nevermined.dev`,staging_live:`https://nevermined.dev`};function i(e){return n[e]}function a(e){return r[e]}var o={MISSING_SESSION:{numericCode:`WDG.0001`,message:`session is required — pass the WidgetSession returned by createWidgetSession on the backend`},INVALID_ENVIRONMENT:{numericCode:`WDG.0002`,message:`environment must be one of: sandbox, live, staging_sandbox, staging_live, local`},INVALID_SESSION:{numericCode:`WDG.0003`,message:`session is missing required fields`},SESSION_EXPIRED:{numericCode:`WDG.0006`,message:`Widget session has expired`}},s=class extends Error{code;constructor(e,t,n){super(t??o[e].message,{cause:n}),this.code=e,this.name=`WidgetInitError`}},c=class extends Error{code=`SESSION_EXPIRED`;constructor(){super(o.SESSION_EXPIRED.message),this.name=`WidgetSessionExpiredError`}},l=class extends Error{status;apiCode;constructor(e,t,n){super(e),this.status=t,this.apiCode=n,this.name=`WidgetApiError`}},u=.8,d=class{session;expiresMs;timer=null;constructor(e){this.session=e,this.expiresMs=new Date(e.expiresAt).getTime(),Number.isNaN(this.expiresMs)&&console.warn(`[SessionManager] malformed expiresAt, session will be treated as expired:`,e.expiresAt)}isValid(){return!Number.isNaN(this.expiresMs)&&this.expiresMs>Date.now()}getToken(){return this.session.sessionToken}getSession(){return this.session}startAutoRefresh(e,t){if(this.stopAutoRefresh(),!this.isValid()){t();return}let n=Math.max(0,Math.floor((this.expiresMs-Date.now())*u));this.timer=setTimeout(()=>{this.timer=null,e().then(n=>{if(this.session=n,this.expiresMs=new Date(n.expiresAt).getTime(),!this.isValid()){t();return}this.startAutoRefresh(e,t)},e=>{console.error(`[SessionManager] session refresh failed:`,e),t()})},n)}stopAutoRefresh(){this.timer!==null&&(clearTimeout(this.timer),this.timer=null)}},f=class{listeners=new Map;on(e,t){let n=this.listeners.get(e)??new Set;return n.add(t),this.listeners.set(e,n),this}off(e,t){let n=this.listeners.get(e);return n?(n.delete(t),n.size===0&&this.listeners.delete(e),this):this}once(e,t){let n=r=>{this.off(e,n),t(r)};return this.on(e,n)}removeAllListeners(e){return e===void 0?this.listeners.clear():this.listeners.delete(e),this}emit(e,t){let n=this.listeners.get(e);n&&[...n].forEach(e=>{try{e(t)}catch(e){console.error(`[TypedEventEmitter] Handler threw:`,e)}})}},p=function(e){return e.INIT=`nvm:init`,e.CLOSE=`nvm:close`,e.BOOTED=`nvm:booted`,e.READY=`nvm:ready`,e.RESIZE=`nvm:resize`,e.SUCCESS=`nvm:success`,e.ERROR=`nvm:error`,e.CARD_ACTION=`nvm:card-action`,e.AUTH_MISMATCH=`nvm:auth-mismatch`,e.AUTH_SWITCH_REQUEST=`nvm:auth-switch-request`,e}({}),m=`1`,h=`1`;function g(e){return typeof e==`string`&&Object.values(p).includes(e)}function _(e,t){return{type:e,version:h,payload:t}}function v(e){if(typeof e!=`object`||!e)return{ok:!1,reason:`not an object`};let t=e;return g(t.type)?t.version===h?{ok:!0,message:{type:t.type,version:t.version,payload:t.payload}}:{ok:!1,reason:`version mismatch: received "${t.version}", expected "${h}"`}:{ok:!1,reason:typeof t.type==`string`&&t.type.startsWith(`nvm:`)?`unknown nvm: type "${t.type}"`:`missing or unknown type`}}function y(e,t,n){return Math.max(t,Math.min(n,Math.round(e)))}var b=class{expectedOrigin;iframe=null;messageHandlers=new Set;protocolErrorHandlers=new Set;messageListener=null;destroyed=!1;constructor(e){if(this.expectedOrigin=e,e===`*`)throw Error(`[IframeManager] Wildcard origin "*" is not allowed — pass an explicit origin`)}create(e,t){this.iframe?.remove();let n=document.createElement(`iframe`);if(n.src=e,n.style.display=`block`,n.setAttribute(`sandbox`,`allow-scripts allow-same-origin allow-forms`),!t?.container)n.style.width=`100%`,n.style.height=`100%`,n.style.border=`none`,n.style.position=`fixed`,n.style.top=`0`,n.style.left=`0`,n.style.right=`0`,n.style.bottom=`0`,n.style.zIndex=`9999`;else{let e=y(t.width??480,440,720),r=y(t.height??720,640,960);n.style.width=`${e}px`,n.style.height=`${r}px`,n.style.border=`1px solid #e5e7eb`,n.style.borderRadius=`8px`,n.style.boxSizing=`border-box`}return t?.style&&Object.assign(n.style,t.style),(t?.container??document.body).appendChild(n),this.iframe=n,n}destroy(){this.destroyed=!0,this.messageListener&&=(window.removeEventListener(`message`,this.messageListener),null),this.messageHandlers.clear(),this.protocolErrorHandlers.clear(),this.iframe?.remove(),this.iframe=null}postMessage(e){this.destroyed||this.iframe?.contentWindow?.postMessage(e,this.expectedOrigin)}onMessage(e){return this.destroyed?()=>void 0:(this.messageHandlers.add(e),this.ensureListener(),()=>this.messageHandlers.delete(e))}onProtocolError(e){return this.destroyed?()=>void 0:(this.protocolErrorHandlers.add(e),this.ensureListener(),()=>this.protocolErrorHandlers.delete(e))}ensureListener(){this.messageListener||(this.messageListener=e=>{if(e.origin!==this.expectedOrigin)return;let t=v(e.data);if(!t.ok){console.warn(`[IframeManager] Discarding message from ${this.expectedOrigin}: ${t.reason}`),this.protocolErrorHandlers.forEach(e=>{try{e(t.reason)}catch(e){console.error(`[IframeManager] Protocol error handler threw:`,e)}});return}this.messageHandlers.forEach(e=>{try{e(t.message)}catch(e){console.error(`[IframeManager] Message handler threw:`,e)}})},window.addEventListener(`message`,this.messageListener))}},x={code:`UNKNOWN`,message:`Unknown widget error`},S=class{session;webappBase;manager=null;destroyed=!1;constructor(e,t){this.session=e,this.webappBase=t}start(e){if(this.destroyed)throw Error(`[CheckoutWidget] cannot start: instance has been destroyed`);if(!e.did||typeof e.did!=`string`)throw Error(`[CheckoutWidget] did is required`);this.manager?.destroy();let t=new URL(this.webappBase).origin,n=window.location.origin,r=new URL(`/embed/checkout/${encodeURIComponent(e.did)}`,this.webappBase);r.searchParams.set(`parentOrigin`,n),e.planId&&r.searchParams.set(`planId`,e.planId);let i=new b(t);this.manager=i,i.create(r.toString(),{container:e.container,width:e.width,height:e.height}),i.onMessage(t=>this.handleMessage(t,e,i))}destroy(){this.destroyed=!0,this.manager?.destroy(),this.manager=null}closeFromIframe(e,t){this.destroyed||this.manager!==t||(this.destroy(),e.onClose?.())}buildSuccessHandle(e,t){return{close:()=>this.closeFromIframe(e,t)}}handleMessage(e,t,n){if(!T(e,t))switch(e.type){case p.BOOTED:this.manager?.postMessage(_(p.INIT,{sessionToken:this.session.getToken()})),t.onBooted?.();return;case p.READY:t.onReady?.();return;case p.SUCCESS:{let r=e.payload??{},i={did:r.did??t.did,planId:r.planId??t.planId,txHash:r.txHash};t.onSuccess?.({result:i,handle:this.buildSuccessHandle(t,n)});return}case p.ERROR:{let n=e.payload;t.onError?.(n?.error??x);return}case p.CLOSE:this.closeFromIframe(t,n);return;default:return}}};function C(e){if(typeof e!=`object`||!e)return null;let t=e.expectedEmail;return typeof t!=`string`||t.length===0?null:{expectedEmail:t}}function w(e){if(typeof e!=`object`||!e)return null;let t=e.requestedEmail,n=e.expectedEmail;return typeof t!=`string`||t.length===0||typeof n!=`string`||n.length===0?null:{requestedEmail:t,expectedEmail:n}}function T(e,t){if(e.type===p.AUTH_MISMATCH){let n=C(e.payload);return n&&t.onAuthMismatch?.(n),!0}if(e.type===p.AUTH_SWITCH_REQUEST){let n=w(e.payload);return n&&t.onAuthSwitchRequest?.(n),!0}return!1}var E={code:`UNKNOWN`,message:`Unknown widget error`},D={code:`UNKNOWN`,message:`Malformed nvm:success payload — paymentMethodId missing or invalid`},O={code:`UNKNOWN`,message:`Malformed nvm:success payload — delegationId missing or invalid`},k=class{session;webappBase;apiBase;manager=null;destroyed=!1;constructor(e,t,n){this.session=e,this.webappBase=t,this.apiBase=n}enrollCard(e){this.assertAlive(`enrollCard`);let t=e.provider??`stripe`,n=this.mountIframe(`/embed/cards/enroll`,e.container,{provider:t},{width:e.width,height:e.height});n.onMessage(t=>this.handleEnrollMessage(t,e,n))}listCards(e){this.assertAlive(`listCards`);let t=this.mountIframe(`/embed/cards/list`,e.container,void 0,{width:e.width,height:e.height});t.onMessage(n=>this.handleListMessage(n,e,t))}createDelegation(e){if(this.assertAlive(`createDelegation`),typeof e.paymentMethodId!=`string`||e.paymentMethodId.length===0)throw Error(`[DelegationsWidget] createDelegation: paymentMethodId is required`);let t=this.mountIframe(`/embed/cards/delegate`,e.container,{paymentMethodId:e.paymentMethodId},{width:e.width,height:e.height});t.onMessage(n=>this.handleCreateDelegationMessage(n,e,t))}async revokeCard(e){if(typeof e!=`string`||e.length===0)throw Error(`[DelegationsWidget] revokeCard: paymentMethodId is required`);await this.deleteWithSession(`${this.apiBase}/api/v1/widgets/payment-methods/${encodeURIComponent(e)}`,`Failed to revoke payment method`)}async revokeDelegation(e){if(typeof e!=`string`||e.length===0)throw Error(`[DelegationsWidget] revokeDelegation: delegationId is required`);await this.deleteWithSession(`${this.apiBase}/api/v1/widgets/delegation/${encodeURIComponent(e)}`,`Failed to revoke delegation`)}destroy(){this.destroyed=!0,this.manager?.destroy(),this.manager=null}assertAlive(e){if(this.destroyed)throw Error(`[DelegationsWidget] cannot ${e}: instance has been destroyed`)}mountIframe(e,t,n={},r={}){this.manager?.destroy();let i=new URL(this.webappBase).origin,a=window.location.origin,o=new URL(e,this.webappBase);o.searchParams.set(`parentOrigin`,a);for(let[e,t]of Object.entries(n))o.searchParams.set(e,t);let s=new b(i);return this.manager=s,s.create(o.toString(),{container:t,width:r.width,height:r.height}),s}postInitOnBooted(){this.manager?.postMessage(_(p.INIT,{sessionToken:this.session.getToken()}))}handleEnrollMessage(e,t,n){if(!T(e,t))switch(e.type){case p.BOOTED:this.postInitOnBooted(),t.onBooted?.();return;case p.READY:t.onReady?.();return;case p.SUCCESS:{let r=(e.payload??{}).paymentMethodId;if(typeof r!=`string`||r.length===0){t.onError?.(D);return}t.onSuccess?.({result:{paymentMethodId:r},handle:this.buildSuccessHandle(t,n)});return}case p.ERROR:{let n=e.payload;t.onError?.(n?.error??E);return}case p.CLOSE:this.closeFromIframe(t,n);return;default:return}}handleListMessage(e,t,n){if(!T(e,t))switch(e.type){case p.BOOTED:this.postInitOnBooted(),t.onBooted?.();return;case p.READY:t.onReady?.();return;case p.CARD_ACTION:{let n=e.payload??{};if(n.action!==`delegate`&&n.action!==`revoked`||typeof n.paymentMethodId!=`string`||n.paymentMethodId.length===0)return;t.onCardAction?.({action:n.action,paymentMethodId:n.paymentMethodId});return}case p.ERROR:{let n=e.payload;t.onError?.(n?.error??E);return}case p.CLOSE:this.closeFromIframe(t,n);return;default:return}}handleCreateDelegationMessage(e,t,n){if(!T(e,t))switch(e.type){case p.BOOTED:this.postInitOnBooted(),t.onBooted?.();return;case p.READY:t.onReady?.();return;case p.SUCCESS:{let r=(e.payload??{}).delegationId;if(typeof r!=`string`||r.length===0){t.onError?.(O);return}t.onSuccess?.({result:{delegationId:r,paymentMethodId:t.paymentMethodId},handle:this.buildSuccessHandle(t,n)});return}case p.ERROR:{let n=e.payload;t.onError?.(n?.error??E);return}case p.CLOSE:this.closeFromIframe(t,n);return;default:return}}closeFromIframe(e,t){this.destroyed||this.manager!==t||(this.destroy(),e.onClose?.())}buildSuccessHandle(e,t){return{close:()=>this.closeFromIframe(e,t)}}async deleteWithSession(e,t){let n;try{n=await fetch(e,{method:`DELETE`,headers:{Authorization:`Bearer ${this.session.getToken()}`,Accept:`application/json`}})}catch(e){throw new l(e instanceof Error&&e.message?e.message:t)}if(n.ok)return;let r,i=t;try{let e=await n.json();typeof e?.message==`string`&&e.message.length>0&&(i=e.message),typeof e?.code==`string`&&(r=e.code)}catch{}throw new l(i,n.status,r)}};function A(e){if(typeof e!=`object`||!e)return!1;let t=e;return[`sessionToken`,`userId`,`expiresAt`,`apiKeyHash`].every(e=>typeof t[e]==`string`)&&typeof t.userWallet==`string`&&t.userWallet.startsWith(`0x`)}var j=class e{sessionManager;_account;environment;_checkout=null;_delegations=null;events=new f;constructor(e,t,n){this.sessionManager=e,this._account=t,this.environment=n}static async initialize(n){let{session:r,environment:i}=n;if(!r||typeof r!=`object`)throw new s(`MISSING_SESSION`);if(!i||!t.includes(i))throw new s(`INVALID_ENVIRONMENT`);if(!A(r))throw new s(`INVALID_SESSION`);let a=new d(r),o=new e(a,{userId:r.userId,userWallet:r.userWallet},i);return a.startAutoRefresh(()=>o.refreshSession(),()=>o.events.emit(`session-expired`,void 0)),o}async refreshSession(){let e=await fetch(`${i(this.environment)}/api/v1/widgets/session/refresh`,{method:`POST`,headers:{Authorization:`Bearer ${this.sessionManager.getToken()}`}});if(!e.ok)throw Error(`Session refresh failed with status ${e.status}`);let t=await e.json();if(!A(t))throw Error(`Session refresh returned an invalid response`);return t}on(e,t){return this.events.on(e,t),this}off(e,t){return this.events.off(e,t),this}get account(){return this._account}get hasValidSession(){return this.sessionManager.isValid()}getSessionToken(){if(!this.sessionManager.isValid())throw new c;return this.sessionManager.getToken()}get checkout(){return this._checkout||=new S(this.sessionManager,a(this.environment)),this._checkout}get delegations(){return this._delegations||=new k(this.sessionManager,a(this.environment),i(this.environment)),this._delegations}destroy(){this.sessionManager.stopAutoRefresh(),this._checkout?.destroy(),this._checkout=null,this._delegations?.destroy(),this._delegations=null,this.events.removeAllListeners()}};e.CheckoutWidget=S,e.DelegationsWidget=k,e.IframeManager=b,e.NeverminedWidgets=j,e.SessionManager=d,e.TypedEventEmitter=f,e.WIDGET_MESSAGE_VERSION=m,e.WidgetApiError=l,e.WidgetInitError=s,e.WidgetMessageType=p,e.WidgetSessionExpiredError=c,e.createMessage=_,e.parseMessage=v});
|
|
1
|
+
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.NeverminedWidgets={}))})(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=[`local`,`sandbox`,`live`,`staging_sandbox`,`staging_live`],n={local:`http://localhost:3001`,sandbox:`https://api.sandbox.nevermined.app`,live:`https://api.live.nevermined.app`,staging_sandbox:`https://api.sandbox.nevermined.dev`,staging_live:`https://api.live.nevermined.dev`},r={local:`http://localhost:4200`,sandbox:`https://nevermined.app`,live:`https://nevermined.app`,staging_sandbox:`https://nevermined.dev`,staging_live:`https://nevermined.dev`};function i(e){return n[e]}function a(e){return r[e]}var o={MISSING_SESSION:{numericCode:`WDG.0001`,message:`session is required — pass the WidgetSession returned by createWidgetSession on the backend`},INVALID_ENVIRONMENT:{numericCode:`WDG.0002`,message:`environment must be one of: sandbox, live, staging_sandbox, staging_live, local`},INVALID_SESSION:{numericCode:`WDG.0003`,message:`session is missing required fields`},SESSION_EXPIRED:{numericCode:`WDG.0006`,message:`Widget session has expired`}},s=class extends Error{code;constructor(e,t,n){super(t??o[e].message,{cause:n}),this.code=e,this.name=`WidgetInitError`}},c=class extends Error{code=`SESSION_EXPIRED`;constructor(){super(o.SESSION_EXPIRED.message),this.name=`WidgetSessionExpiredError`}},l=class extends Error{status;apiCode;constructor(e,t,n){super(e),this.status=t,this.apiCode=n,this.name=`WidgetApiError`}},u=.8,d=class{session;expiresMs;timer=null;constructor(e){this.session=e,this.expiresMs=new Date(e.expiresAt).getTime(),Number.isNaN(this.expiresMs)&&console.warn(`[SessionManager] malformed expiresAt, session will be treated as expired:`,e.expiresAt)}isValid(){return!Number.isNaN(this.expiresMs)&&this.expiresMs>Date.now()}getToken(){return this.session.sessionToken}getSession(){return this.session}startAutoRefresh(e,t){if(this.stopAutoRefresh(),!this.isValid()){t();return}let n=Math.max(0,Math.floor((this.expiresMs-Date.now())*u));this.timer=setTimeout(()=>{this.timer=null,e().then(n=>{if(this.session=n,this.expiresMs=new Date(n.expiresAt).getTime(),!this.isValid()){t();return}this.startAutoRefresh(e,t)},e=>{console.error(`[SessionManager] session refresh failed:`,e),t()})},n)}stopAutoRefresh(){this.timer!==null&&(clearTimeout(this.timer),this.timer=null)}},f=class{listeners=new Map;on(e,t){let n=this.listeners.get(e)??new Set;return n.add(t),this.listeners.set(e,n),this}off(e,t){let n=this.listeners.get(e);return n?(n.delete(t),n.size===0&&this.listeners.delete(e),this):this}once(e,t){let n=r=>{this.off(e,n),t(r)};return this.on(e,n)}removeAllListeners(e){return e===void 0?this.listeners.clear():this.listeners.delete(e),this}emit(e,t){let n=this.listeners.get(e);n&&[...n].forEach(e=>{try{e(t)}catch(e){console.error(`[TypedEventEmitter] Handler threw:`,e)}})}},p=function(e){return e.INIT=`nvm:init`,e.CLOSE=`nvm:close`,e.BOOTED=`nvm:booted`,e.READY=`nvm:ready`,e.RESIZE=`nvm:resize`,e.SUCCESS=`nvm:success`,e.ERROR=`nvm:error`,e.CARD_ACTION=`nvm:card-action`,e.AUTH_MISMATCH=`nvm:auth-mismatch`,e.AUTH_SWITCH_REQUEST=`nvm:auth-switch-request`,e}({}),m=`1`,h=`1`;function g(e){return typeof e==`string`&&Object.values(p).includes(e)}function _(e,t){return{type:e,version:h,payload:t}}function v(e){if(typeof e!=`object`||!e)return{ok:!1,reason:`not an object`};let t=e;return g(t.type)?t.version===h?{ok:!0,message:{type:t.type,version:t.version,payload:t.payload}}:{ok:!1,reason:`version mismatch: received "${t.version}", expected "${h}"`}:{ok:!1,reason:typeof t.type==`string`&&t.type.startsWith(`nvm:`)?`unknown nvm: type "${t.type}"`:`missing or unknown type`}}function y(e,t,n){return Math.max(t,Math.min(n,Math.round(e)))}var b=class{expectedOrigin;iframe=null;messageHandlers=new Set;protocolErrorHandlers=new Set;messageListener=null;destroyed=!1;constructor(e){if(this.expectedOrigin=e,e===`*`)throw Error(`[IframeManager] Wildcard origin "*" is not allowed — pass an explicit origin`)}create(e,t){this.iframe?.remove();let n=document.createElement(`iframe`);if(n.src=e,n.style.display=`block`,n.setAttribute(`sandbox`,`allow-scripts allow-same-origin allow-forms`),!t?.container)n.style.width=`100%`,n.style.height=`100%`,n.style.border=`none`,n.style.position=`fixed`,n.style.top=`0`,n.style.left=`0`,n.style.right=`0`,n.style.bottom=`0`,n.style.zIndex=`9999`;else{let e=y(t.width??480,440,720),r=y(t.height??720,640,960);n.style.width=`${e}px`,n.style.height=`${r}px`,n.style.border=`1px solid #e5e7eb`,n.style.borderRadius=`8px`,n.style.boxSizing=`border-box`}return t?.style&&Object.assign(n.style,t.style),(t?.container??document.body).appendChild(n),this.iframe=n,n}destroy(){this.destroyed=!0,this.messageListener&&=(window.removeEventListener(`message`,this.messageListener),null),this.messageHandlers.clear(),this.protocolErrorHandlers.clear(),this.iframe?.remove(),this.iframe=null}postMessage(e){this.destroyed||this.iframe?.contentWindow?.postMessage(e,this.expectedOrigin)}onMessage(e){return this.destroyed?()=>void 0:(this.messageHandlers.add(e),this.ensureListener(),()=>this.messageHandlers.delete(e))}onProtocolError(e){return this.destroyed?()=>void 0:(this.protocolErrorHandlers.add(e),this.ensureListener(),()=>this.protocolErrorHandlers.delete(e))}ensureListener(){this.messageListener||(this.messageListener=e=>{if(e.origin!==this.expectedOrigin)return;let t=v(e.data);if(!t.ok){console.warn(`[IframeManager] Discarding message from ${this.expectedOrigin}: ${t.reason}`),this.protocolErrorHandlers.forEach(e=>{try{e(t.reason)}catch(e){console.error(`[IframeManager] Protocol error handler threw:`,e)}});return}this.messageHandlers.forEach(e=>{try{e(t.message)}catch(e){console.error(`[IframeManager] Message handler threw:`,e)}})},window.addEventListener(`message`,this.messageListener))}},x={code:`UNKNOWN`,message:`Unknown widget error`},S=class{session;webappBase;manager=null;destroyed=!1;constructor(e,t){this.session=e,this.webappBase=t}start(e){if(this.destroyed)throw Error(`[CheckoutWidget] cannot start: instance has been destroyed`);if(!e.did||typeof e.did!=`string`)throw Error(`[CheckoutWidget] did is required`);let t=new URL(`/embed/checkout/${encodeURIComponent(e.did)}`,this.webappBase);e.planId&&t.searchParams.set(`planId`,e.planId),this.mount(t,e,t=>({did:t.did??e.did,planId:t.planId??e.planId,txHash:t.txHash}),e.onSuccess)}startPlan(e){if(this.destroyed)throw Error(`[CheckoutWidget] cannot start: instance has been destroyed`);if(!e.planId||typeof e.planId!=`string`)throw Error(`[CheckoutWidget] planId is required`);let t=new URL(`/embed/checkout/plan/${encodeURIComponent(e.planId)}`,this.webappBase);this.mount(t,e,t=>({planId:t.planId??e.planId,txHash:t.txHash}),e.onSuccess)}destroy(){this.destroyed=!0,this.manager?.destroy(),this.manager=null}closeFromIframe(e,t){this.destroyed||this.manager!==t||(this.destroy(),e.onClose?.())}buildSuccessHandle(e,t){return{close:()=>this.closeFromIframe(e,t)}}mount(e,t,n,r){this.manager?.destroy();let i=new URL(this.webappBase).origin;e.searchParams.set(`parentOrigin`,window.location.origin);let a=new b(i);this.manager=a,a.create(e.toString(),{container:t.container,width:t.width,height:t.height}),a.onMessage(e=>this.handleMessage(e,t,a,n,r))}handleMessage(e,t,n,r,i){if(!T(e,t))switch(e.type){case p.BOOTED:this.manager?.postMessage(_(p.INIT,{sessionToken:this.session.getToken()})),t.onBooted?.();return;case p.READY:t.onReady?.();return;case p.SUCCESS:{let a=r(e.payload!=null&&typeof e.payload==`object`?e.payload:{});i?.({result:a,handle:this.buildSuccessHandle(t,n)});return}case p.ERROR:{let n=e.payload;t.onError?.(n?.error??x);return}case p.CLOSE:this.closeFromIframe(t,n);return;default:return}}};function C(e){if(typeof e!=`object`||!e)return null;let t=e.expectedEmail;return typeof t!=`string`||t.length===0?null:{expectedEmail:t}}function w(e){if(typeof e!=`object`||!e)return null;let t=e.requestedEmail,n=e.expectedEmail;return typeof t!=`string`||t.length===0||typeof n!=`string`||n.length===0?null:{requestedEmail:t,expectedEmail:n}}function T(e,t){if(e.type===p.AUTH_MISMATCH){let n=C(e.payload);return n&&t.onAuthMismatch?.(n),!0}if(e.type===p.AUTH_SWITCH_REQUEST){let n=w(e.payload);return n&&t.onAuthSwitchRequest?.(n),!0}return!1}var E={code:`UNKNOWN`,message:`Unknown widget error`},D={code:`UNKNOWN`,message:`Malformed nvm:success payload — paymentMethodId missing or invalid`},O={code:`UNKNOWN`,message:`Malformed nvm:success payload — delegationId missing or invalid`},k=class{session;webappBase;apiBase;manager=null;destroyed=!1;constructor(e,t,n){this.session=e,this.webappBase=t,this.apiBase=n}enrollCard(e){this.assertAlive(`enrollCard`);let t=e.provider??`stripe`,n=this.mountIframe(`/embed/cards/enroll`,e.container,{provider:t},{width:e.width,height:e.height});n.onMessage(t=>this.handleEnrollMessage(t,e,n))}listCards(e){this.assertAlive(`listCards`);let t=this.mountIframe(`/embed/cards/list`,e.container,void 0,{width:e.width,height:e.height});t.onMessage(n=>this.handleListMessage(n,e,t))}createDelegation(e){if(this.assertAlive(`createDelegation`),typeof e.paymentMethodId!=`string`||e.paymentMethodId.length===0)throw Error(`[DelegationsWidget] createDelegation: paymentMethodId is required`);let t=this.mountIframe(`/embed/cards/delegate`,e.container,{paymentMethodId:e.paymentMethodId},{width:e.width,height:e.height});t.onMessage(n=>this.handleCreateDelegationMessage(n,e,t))}async revokeCard(e){if(typeof e!=`string`||e.length===0)throw Error(`[DelegationsWidget] revokeCard: paymentMethodId is required`);await this.deleteWithSession(`${this.apiBase}/api/v1/widgets/payment-methods/${encodeURIComponent(e)}`,`Failed to revoke payment method`)}async revokeDelegation(e){if(typeof e!=`string`||e.length===0)throw Error(`[DelegationsWidget] revokeDelegation: delegationId is required`);await this.deleteWithSession(`${this.apiBase}/api/v1/widgets/delegation/${encodeURIComponent(e)}`,`Failed to revoke delegation`)}destroy(){this.destroyed=!0,this.manager?.destroy(),this.manager=null}assertAlive(e){if(this.destroyed)throw Error(`[DelegationsWidget] cannot ${e}: instance has been destroyed`)}mountIframe(e,t,n={},r={}){this.manager?.destroy();let i=new URL(this.webappBase).origin,a=window.location.origin,o=new URL(e,this.webappBase);o.searchParams.set(`parentOrigin`,a);for(let[e,t]of Object.entries(n))o.searchParams.set(e,t);let s=new b(i);return this.manager=s,s.create(o.toString(),{container:t,width:r.width,height:r.height}),s}postInitOnBooted(){this.manager?.postMessage(_(p.INIT,{sessionToken:this.session.getToken()}))}handleEnrollMessage(e,t,n){if(!T(e,t))switch(e.type){case p.BOOTED:this.postInitOnBooted(),t.onBooted?.();return;case p.READY:t.onReady?.();return;case p.SUCCESS:{let r=(e.payload??{}).paymentMethodId;if(typeof r!=`string`||r.length===0){t.onError?.(D);return}t.onSuccess?.({result:{paymentMethodId:r},handle:this.buildSuccessHandle(t,n)});return}case p.ERROR:{let n=e.payload;t.onError?.(n?.error??E);return}case p.CLOSE:this.closeFromIframe(t,n);return;default:return}}handleListMessage(e,t,n){if(!T(e,t))switch(e.type){case p.BOOTED:this.postInitOnBooted(),t.onBooted?.();return;case p.READY:t.onReady?.();return;case p.CARD_ACTION:{let n=e.payload??{};if(n.action!==`delegate`&&n.action!==`revoked`||typeof n.paymentMethodId!=`string`||n.paymentMethodId.length===0)return;t.onCardAction?.({action:n.action,paymentMethodId:n.paymentMethodId});return}case p.ERROR:{let n=e.payload;t.onError?.(n?.error??E);return}case p.CLOSE:this.closeFromIframe(t,n);return;default:return}}handleCreateDelegationMessage(e,t,n){if(!T(e,t))switch(e.type){case p.BOOTED:this.postInitOnBooted(),t.onBooted?.();return;case p.READY:t.onReady?.();return;case p.SUCCESS:{let r=(e.payload??{}).delegationId;if(typeof r!=`string`||r.length===0){t.onError?.(O);return}t.onSuccess?.({result:{delegationId:r,paymentMethodId:t.paymentMethodId},handle:this.buildSuccessHandle(t,n)});return}case p.ERROR:{let n=e.payload;t.onError?.(n?.error??E);return}case p.CLOSE:this.closeFromIframe(t,n);return;default:return}}closeFromIframe(e,t){this.destroyed||this.manager!==t||(this.destroy(),e.onClose?.())}buildSuccessHandle(e,t){return{close:()=>this.closeFromIframe(e,t)}}async deleteWithSession(e,t){let n;try{n=await fetch(e,{method:`DELETE`,headers:{Authorization:`Bearer ${this.session.getToken()}`,Accept:`application/json`}})}catch(e){throw new l(e instanceof Error&&e.message?e.message:t)}if(n.ok)return;let r,i=t;try{let e=await n.json();typeof e?.message==`string`&&e.message.length>0&&(i=e.message),typeof e?.code==`string`&&(r=e.code)}catch{}throw new l(i,n.status,r)}};function A(e){if(typeof e!=`object`||!e)return!1;let t=e;return[`sessionToken`,`userId`,`expiresAt`,`apiKeyHash`].every(e=>typeof t[e]==`string`)&&typeof t.userWallet==`string`&&t.userWallet.startsWith(`0x`)}var j=class e{sessionManager;_account;environment;_checkout=null;_delegations=null;events=new f;constructor(e,t,n){this.sessionManager=e,this._account=t,this.environment=n}static async initialize(n){let{session:r,environment:i}=n;if(!r||typeof r!=`object`)throw new s(`MISSING_SESSION`);if(!i||!t.includes(i))throw new s(`INVALID_ENVIRONMENT`);if(!A(r))throw new s(`INVALID_SESSION`);let a=new d(r),o=new e(a,{userId:r.userId,userWallet:r.userWallet},i);return a.startAutoRefresh(()=>o.refreshSession(),()=>o.events.emit(`session-expired`,void 0)),o}async refreshSession(){let e=await fetch(`${i(this.environment)}/api/v1/widgets/session/refresh`,{method:`POST`,headers:{Authorization:`Bearer ${this.sessionManager.getToken()}`}});if(!e.ok)throw Error(`Session refresh failed with status ${e.status}`);let t=await e.json();if(!A(t))throw Error(`Session refresh returned an invalid response`);return t}on(e,t){return this.events.on(e,t),this}off(e,t){return this.events.off(e,t),this}get account(){return this._account}get hasValidSession(){return this.sessionManager.isValid()}getSessionToken(){if(!this.sessionManager.isValid())throw new c;return this.sessionManager.getToken()}get checkout(){return this._checkout||=new S(this.sessionManager,a(this.environment)),this._checkout}get delegations(){return this._delegations||=new k(this.sessionManager,a(this.environment),i(this.environment)),this._delegations}destroy(){this.sessionManager.stopAutoRefresh(),this._checkout?.destroy(),this._checkout=null,this._delegations?.destroy(),this._delegations=null,this.events.removeAllListeners()}};e.CheckoutWidget=S,e.DelegationsWidget=k,e.IframeManager=b,e.NeverminedWidgets=j,e.SessionManager=d,e.TypedEventEmitter=f,e.WIDGET_MESSAGE_VERSION=m,e.WidgetApiError=l,e.WidgetInitError=s,e.WidgetMessageType=p,e.WidgetSessionExpiredError=c,e.createMessage=_,e.parseMessage=v});
|