@ledewire/browser 0.4.0 → 0.6.0

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 CHANGED
@@ -40,15 +40,15 @@ const lw = init({
40
40
 
41
41
  ## Client Namespaces
42
42
 
43
- | Namespace | Description |
44
- | ------------------- | --------------------------------------------------------------- |
45
- | `lw.config` | Platform public config (no auth required) |
46
- | `lw.auth` | Buyer signup, email/password login, Google OAuth, API key login |
47
- | `lw.wallet` | Wallet balance, fund wallet via payment session |
48
- | `lw.purchases` | List and create content purchases |
49
- | `lw.content` | Fetch content with buyer access info |
50
- | `lw.checkout` | Checkout state — what action is required next |
51
- | `lw.seller.content` | List, search, and get store content (API key auth) |
43
+ | Namespace | Description |
44
+ | ------------------- | ------------------------------------------------------------------------------- |
45
+ | `lw.config` | Platform public config (no auth required) |
46
+ | `lw.auth` | Buyer signup, email/password login, Google OAuth, API key login, password reset |
47
+ | `lw.wallet` | Wallet balance, fund wallet via payment session |
48
+ | `lw.purchases` | List and create content purchases |
49
+ | `lw.content` | Fetch content with buyer access info |
50
+ | `lw.checkout` | Checkout state — what action is required next |
51
+ | `lw.seller.content` | List, search, and get store content (API key auth) |
52
52
 
53
53
  ## Example: Fetch Google OAuth Client ID Before Sign-In
54
54
 
@@ -83,8 +83,8 @@ switch (checkout_state.next_required_action) {
83
83
  const { content_type, content_body, content_uri } =
84
84
  await lw.content.getWithAccess('article-123')
85
85
  if (content_type === 'markdown') {
86
- // render decoded markdown
87
- renderMarkdown(atob(content_body))
86
+ // content_body is plain text — the SDK decodes base64 automatically
87
+ renderMarkdown(content_body)
88
88
  } else {
89
89
  // redirect to the gated external URI (Vimeo, PDF, etc.)
90
90
  window.location.href = content_uri
@@ -95,14 +95,14 @@ switch (checkout_state.next_required_action) {
95
95
 
96
96
  ## Example: Seller Content Discovery
97
97
 
98
- Use `lw.auth.loginWithApiKey` with only the `key` to obtain a read-only token,
98
+ Use `lw.seller.loginWithApiKey` with only the `key` to obtain a read-only token,
99
99
  then browse your store's content catalogue directly from the browser:
100
100
 
101
101
  ```ts
102
102
  const lw = Ledewire.init({ apiKey: 'your_api_key' })
103
103
 
104
104
  // Obtain a view-only seller token (key only — no secret needed)
105
- await lw.auth.loginWithApiKey({ key: 'your_api_key' })
105
+ await lw.seller.loginWithApiKey({ key: 'your_api_key' })
106
106
 
107
107
  // List all content
108
108
  const items = await lw.seller.content.list()
@@ -118,11 +118,16 @@ const item = await lw.seller.content.get('content-id')
118
118
  ## Token Storage
119
119
 
120
120
  By default tokens are stored **in memory** (most secure — cleared on page unload).
121
- Use the built-in `localStorageAdapter` to persist sessions across reloads:
121
+ Two built-in adapters are available for persistent sessions:
122
122
 
123
123
  ```ts
124
- import { init, localStorageAdapter } from '@ledewire/browser'
124
+ import { init, localStorageAdapter, sessionStorageAdapter } from '@ledewire/browser'
125
+
126
+ // Persists across tabs and browser restarts
125
127
  const lw = init({ apiKey: '...', storage: localStorageAdapter() })
128
+
129
+ // Persists within the current tab only (cleared on tab close)
130
+ const lw = init({ apiKey: '...', storage: sessionStorageAdapter() })
126
131
  ```
127
132
 
128
133
  Token refresh is handled automatically — you never need to call a refresh method manually.
package/dist/index.d.ts CHANGED
@@ -56,11 +56,43 @@ declare type AuthLoginEmailRequest = components['schemas']['AuthLoginEmailReques
56
56
  /** Request body for buyer Google OAuth login. */
57
57
  declare type AuthLoginOAuthRequest = components['schemas']['AuthLoginOAuthRequest'];
58
58
 
59
+ /**
60
+ * Request body for completing a buyer password reset.
61
+ * Sent to `POST /v1/auth/password/reset`.
62
+ */
63
+ export declare interface AuthPasswordResetBody {
64
+ /** The buyer's registered email address. */
65
+ email: string;
66
+ /** 6-digit numeric code delivered to the buyer's email. */
67
+ reset_code: string;
68
+ /** New password (minimum 6 characters). */
69
+ password: string;
70
+ }
71
+
72
+ /**
73
+ * Request body for initiating a buyer password reset.
74
+ * Sent to `POST /v1/auth/password/reset-request`.
75
+ */
76
+ export declare interface AuthPasswordResetRequestBody {
77
+ /** The buyer's registered email address. */
78
+ email: string;
79
+ }
80
+
81
+ /**
82
+ * Response returned by both password reset endpoints.
83
+ * Contains a human-readable confirmation message.
84
+ */
85
+ export declare interface AuthPasswordResetResponse {
86
+ data?: {
87
+ message?: string;
88
+ };
89
+ }
90
+
59
91
  /** Request body for buyer email/password signup. */
60
92
  declare type AuthSignupRequest = components['schemas']['AuthSignupRequest'];
61
93
 
62
94
  /**
63
- * Buyer authentication: signup, email/password login, Google OAuth.
95
+ * Buyer authentication: signup, email/password login, Google OAuth, password reset.
64
96
  *
65
97
  * Obtain via `lw.auth` — do not construct directly.
66
98
  *
@@ -99,26 +131,39 @@ declare class BrowserAuthNamespace {
99
131
  */
100
132
  loginWithGoogle(body: AuthLoginOAuthRequest): Promise<AuthenticationResponse>;
101
133
  /**
102
- * Log in using an API key to obtain a seller token.
103
- * Provide only `key` for read-only (`view`) access.
104
- * Provide both `key` and `secret` for read/write (`full`) access.
105
- * Tokens are stored automatically after successful authentication.
134
+ * Request a password reset code to be sent to the buyer's email address.
106
135
  *
107
- * Use this before calling `lw.seller.content.*` methods.
136
+ * The response is intentionally ambiguous — if the email does not exist,
137
+ * the API returns the same success message to prevent account enumeration.
108
138
  *
109
- * @param body - API key credentials.
110
- * @returns The authentication token response.
139
+ * @param body - The buyer's email address.
140
+ * @returns A response with a confirmation message.
111
141
  *
112
142
  * @example
113
143
  * ```ts
114
- * // View access (read-only) — sufficient for seller.content.list/search/get
115
- * await lw.auth.loginWithApiKey({ key: 'your_api_key' })
144
+ * await lw.auth.requestPasswordReset({ email: 'buyer@example.com' })
145
+ * // → { data: { message: 'If an account with this email exists, a reset code has been sent.' } }
146
+ * ```
147
+ */
148
+ requestPasswordReset(body: AuthPasswordResetRequestBody): Promise<AuthPasswordResetResponse>;
149
+ /**
150
+ * Reset a buyer's password using the code delivered to their email.
116
151
  *
117
- * // Full access (read/write)
118
- * await lw.auth.loginWithApiKey({ key: 'your_api_key', secret: 'your_secret' })
152
+ * Obtain the reset code first by calling `requestPasswordReset()`.
153
+ *
154
+ * @param body - Email address, 6-digit reset code, and the new password.
155
+ * @returns A response with a confirmation message.
156
+ *
157
+ * @example
158
+ * ```ts
159
+ * await lw.auth.resetPassword({
160
+ * email: 'buyer@example.com',
161
+ * reset_code: '123456',
162
+ * password: 'new-secure-password',
163
+ * })
119
164
  * ```
120
165
  */
121
- loginWithApiKey(body: AuthLoginApiKeyRequest): Promise<AuthenticationResponse>;
166
+ resetPassword(body: AuthPasswordResetBody): Promise<AuthPasswordResetResponse>;
122
167
  private storeTokens;
123
168
  }
124
169
 
@@ -135,7 +180,7 @@ declare class BrowserClient {
135
180
  readonly _config: BrowserClientConfig;
136
181
  /** Platform-level public configuration (no auth required) */
137
182
  readonly config: BrowserConfigNamespace;
138
- /** Buyer authentication: email/password signup/login, Google, password reset */
183
+ /** Buyer authentication: email/password signup/login, Google OAuth, password reset */
139
184
  readonly auth: BrowserAuthNamespace;
140
185
  /** Checkout state machine: determines next action for a piece of content */
141
186
  readonly checkout: CheckoutNamespace;
@@ -145,7 +190,7 @@ declare class BrowserClient {
145
190
  readonly purchases: BrowserPurchasesNamespace;
146
191
  /** Public content with per-user access information */
147
192
  readonly content: BrowserContentNamespace;
148
- /** Seller content: list, search, and get (requires API key view token) */
193
+ /** Seller operations: API key login, content list/search/get */
149
194
  readonly seller: BrowserSellerNamespace;
150
195
  /* Excluded from this release type: __constructor */
151
196
  }
@@ -220,8 +265,10 @@ declare class BrowserConfigNamespace {
220
265
  *
221
266
  * @example
222
267
  * ```ts
223
- * const { content, access } = await lw.content.getWithAccess('content-id')
224
- * if (access.has_access) showFullContent(content)
268
+ * const result = await lw.content.getWithAccess('content-id')
269
+ * if (result.access_info.next_required_action === 'view_content') {
270
+ * renderMarkdown(result.content_body ?? '')
271
+ * }
225
272
  * ```
226
273
  */
227
274
  declare class BrowserContentNamespace {
@@ -232,10 +279,19 @@ declare class BrowserContentNamespace {
232
279
  * authenticated buyer (or a generic access response when unauthenticated).
233
280
  *
234
281
  * @param id - The content ID.
235
- * @param userId - Optional user ID to check a specific user's access status.
236
- * @returns The content item with access information.
282
+ * @returns The content item with access information. `content_body` and
283
+ * `teaser` are returned as plain UTF-8 text — the SDK decodes base64
284
+ * transparently so you can render them directly.
285
+ *
286
+ * @example
287
+ * ```ts
288
+ * const result = await lw.content.getWithAccess('article-123')
289
+ * if (result.access_info.next_required_action === 'view_content') {
290
+ * renderMarkdown(result.content_body ?? '')
291
+ * }
292
+ * ```
237
293
  */
238
- getWithAccess(id: string, userId?: string): Promise<ContentWithAccessResponse>;
294
+ getWithAccess(id: string): Promise<ContentWithAccessResponse>;
239
295
  }
240
296
 
241
297
  /**
@@ -282,7 +338,7 @@ declare class BrowserPurchasesNamespace {
282
338
  * @example
283
339
  * ```ts
284
340
  * const lw = Ledewire.init({ apiKey: 'your_api_key' })
285
- * await lw.auth.loginWithApiKey({ key: 'your_api_key' })
341
+ * await lw.seller.loginWithApiKey({ key: 'your_api_key' })
286
342
  *
287
343
  * const items = await lw.seller.content.list()
288
344
  * const results = await lw.seller.content.search({ title: 'intro' })
@@ -296,7 +352,8 @@ declare class BrowserSellerContentNamespace {
296
352
  * List all content for the authenticated seller's store.
297
353
  * Requires a token with at least `view` permission.
298
354
  *
299
- * @returns Array of content items.
355
+ * @returns Array of content items. `teaser` is returned as plain text —
356
+ * the SDK decodes base64 automatically.
300
357
  *
301
358
  * @example
302
359
  * ```ts
@@ -310,12 +367,14 @@ declare class BrowserSellerContentNamespace {
310
367
  * Requires a token with at least `view` permission.
311
368
  *
312
369
  * @param body - Search criteria (title, uri, and/or metadata).
313
- * @returns Array of matching content items.
370
+ * @returns Array of matching content items. `teaser` is returned as plain
371
+ * text — the SDK decodes base64 automatically.
314
372
  *
315
373
  * @example
316
374
  * ```ts
317
375
  * const results = await lw.seller.content.search({ title: 'intro' })
318
376
  * const byUri = await lw.seller.content.search({ uri: 'vimeo.com' })
377
+ * const byId = await lw.seller.content.search({ external_identifier: 'vimeo:123456789' })
319
378
  * const byMeta = await lw.seller.content.search({ metadata: { author: 'Alice' } })
320
379
  * ```
321
380
  */
@@ -325,11 +384,14 @@ declare class BrowserSellerContentNamespace {
325
384
  * Requires a token with at least `view` permission.
326
385
  *
327
386
  * @param id - The content ID.
328
- * @returns The content item.
387
+ * @returns The content item. `content_body` and `teaser` are returned as
388
+ * plain UTF-8 text — the SDK decodes base64 automatically so you can render
389
+ * them directly without calling `atob()`.
329
390
  *
330
391
  * @example
331
392
  * ```ts
332
393
  * const item = await lw.seller.content.get('content-id')
394
+ * // item.content_body is plain markdown — no atob() needed
333
395
  * ```
334
396
  */
335
397
  get(id: string): Promise<ContentResponse>;
@@ -343,14 +405,39 @@ declare class BrowserSellerContentNamespace {
343
405
  * @example
344
406
  * ```ts
345
407
  * const lw = Ledewire.init({ apiKey: 'your_api_key' })
346
- * await lw.auth.loginWithApiKey({ key: 'your_api_key' })
408
+ *
409
+ * // Authenticate as a seller (key-only = view; key+secret = full)
410
+ * await lw.seller.loginWithApiKey({ key: 'your_api_key' })
347
411
  * const items = await lw.seller.content.list()
348
412
  * ```
349
413
  */
350
414
  declare class BrowserSellerNamespace {
415
+ private readonly http;
416
+ private readonly tokenManager;
351
417
  /** Seller content: list, search, and get by API key. */
352
418
  readonly content: BrowserSellerContentNamespace;
353
419
  /* Excluded from this release type: __constructor */
420
+ /**
421
+ * Log in using an API key to obtain a seller token.
422
+ * Provide only `key` for read-only (`view`) access.
423
+ * Provide both `key` and `secret` for read/write (`full`) access.
424
+ * Tokens are stored automatically after successful authentication.
425
+ *
426
+ * Use this before calling `lw.seller.content.*` methods.
427
+ *
428
+ * @param body - API key credentials.
429
+ * @returns The authentication token response.
430
+ *
431
+ * @example
432
+ * ```ts
433
+ * // View access (read-only) — sufficient for seller.content.list/search/get
434
+ * await lw.seller.loginWithApiKey({ key: 'your_api_key' })
435
+ *
436
+ * // Full access (read/write)
437
+ * await lw.seller.loginWithApiKey({ key: 'your_api_key', secret: 'your_secret' })
438
+ * ```
439
+ */
440
+ loginWithApiKey(body: AuthLoginApiKeyRequest): Promise<AuthenticationResponse>;
354
441
  }
355
442
 
356
443
  /**
@@ -431,16 +518,19 @@ declare class CheckoutNamespace {
431
518
  */
432
519
  export declare type CheckoutNextAction = 'authenticate' | 'fund_wallet' | 'purchase' | 'view_content';
433
520
 
434
- /** Checkout state machine result for a specific content item. */
435
- export declare interface CheckoutState {
436
- content_id: string;
437
- checkout_state: {
438
- is_authenticated: boolean;
439
- has_sufficient_funds: boolean;
440
- has_purchased: boolean;
441
- next_required_action: CheckoutNextAction;
442
- };
443
- }
521
+ /**
522
+ * Checkout state machine result for a specific content item, as returned by
523
+ * `lw.checkout.state()`.
524
+ *
525
+ * This is a consumer-facing alias for {@link CheckoutStateResponse} — the two
526
+ * types are identical. Prefer `CheckoutState` in application code; use
527
+ * `CheckoutStateResponse` when you need to explicitly reference the OpenAPI
528
+ * schema name.
529
+ *
530
+ * Note: `checkout_state.has_sufficient_funds` is `boolean | null` because the
531
+ * API omits or nulls the field when the buyer is unauthenticated.
532
+ */
533
+ export declare type CheckoutState = CheckoutStateResponse;
444
534
 
445
535
  /** Full checkout state for a buyer/content pair, including auth and fund status. */
446
536
  export declare type CheckoutStateResponse = components['schemas']['CheckoutStateResponse'];
@@ -622,7 +712,9 @@ declare interface components {
622
712
  */
623
713
  currency: string;
624
714
  /** @description Additional metadata to include with the payment session */
625
- metadata?: Record<string, never>;
715
+ metadata?: {
716
+ [key: string]: unknown;
717
+ };
626
718
  };
627
719
  WalletPaymentSessionResponse: {
628
720
  /** @description The client secret used by the payment widget to confirm the payment */
@@ -639,7 +731,9 @@ declare interface components {
639
731
  /** @description Type of event (e.g., payment_intent.succeeded, payment_intent.payment_failed) */
640
732
  type?: string;
641
733
  /** @description Event data containing the object that triggered the event */
642
- data?: Record<string, never>;
734
+ data?: {
735
+ [key: string]: unknown;
736
+ };
643
737
  };
644
738
  WalletBalanceResponse: {
645
739
  balance_cents: number;
@@ -1247,7 +1341,7 @@ declare interface components {
1247
1341
  export declare type ContentAccessInfo = components['schemas']['ContentAccessInfo'];
1248
1342
 
1249
1343
  /** Full content item returned by all seller content endpoints. */
1250
- declare type ContentResponse = components['schemas']['ContentResponse'];
1344
+ export declare type ContentResponse = components['schemas']['ContentResponse'];
1251
1345
 
1252
1346
  /** A piece of content with buyer access information. */
1253
1347
  export declare type ContentWithAccessResponse = components['schemas']['ContentWithAccessResponse'];
@@ -1267,6 +1361,25 @@ export declare type ContentWithAccessResponse = components['schemas']['ContentWi
1267
1361
  *
1268
1362
  * @example
1269
1363
  * ```ts
1364
+ * // Browser
1365
+ * import { ForbiddenError, AuthError } from '@ledewire/browser'
1366
+ *
1367
+ * try {
1368
+ * await lw.auth.loginWithGoogle({ id_token })
1369
+ * } catch (err) {
1370
+ * if (err instanceof ForbiddenError) {
1371
+ * // Credentials were valid but account lacks the required role.
1372
+ * console.error('Access denied:', err.message)
1373
+ * } else if (err instanceof AuthError) {
1374
+ * // Bad credentials or expired token — re-authenticate.
1375
+ * console.error('Authentication failed:', err.message)
1376
+ * }
1377
+ * }
1378
+ * ```
1379
+ *
1380
+ * @example
1381
+ * ```ts
1382
+ * // Node
1270
1383
  * import { ForbiddenError, AuthError } from '@ledewire/node'
1271
1384
  *
1272
1385
  * try {
@@ -1488,10 +1601,45 @@ export declare interface SellerContentSearchRequest {
1488
1601
  title?: string;
1489
1602
  /** Case-insensitive partial match against the content URI (`external_ref` content only). */
1490
1603
  uri?: string;
1604
+ /**
1605
+ * Exact match against the content's external identifier.
1606
+ * Use the full formatted value as it appears in `ContentResponse.external_identifier`,
1607
+ * e.g. `'vimeo:123456789'`.
1608
+ */
1609
+ external_identifier?: string;
1491
1610
  /** Exact key/value pairs to AND-match against content metadata. */
1492
1611
  metadata?: Record<string, unknown>;
1493
1612
  }
1494
1613
 
1614
+ /**
1615
+ * A {@link TokenStorage} adapter that persists tokens in `sessionStorage`.
1616
+ *
1617
+ * Tokens survive page reloads within the same tab but are cleared when the
1618
+ * tab is closed or the session ends. This prevents token leakage across
1619
+ * tabs and is a good default for sites where users do not expect persistent
1620
+ * sessions (e.g. embedded checkout widgets, kiosk-mode UIs).
1621
+ *
1622
+ * **Security note:** `sessionStorage` is accessible to any JavaScript on the
1623
+ * same origin and tab. It is more isolated than `localStorage` (no
1624
+ * cross-tab sharing), but the default in-memory storage is still safer for
1625
+ * high-security use cases.
1626
+ *
1627
+ * @param key - The `sessionStorage` key used to store tokens.
1628
+ * Defaults to `'lw:tokens'`. Override this if you have multiple
1629
+ * LedeWire integrations on the same origin.
1630
+ *
1631
+ * @example
1632
+ * ```ts
1633
+ * import { init, sessionStorageAdapter } from '@ledewire/browser'
1634
+ *
1635
+ * const lw = init({
1636
+ * apiKey: 'your_api_key',
1637
+ * storage: sessionStorageAdapter(),
1638
+ * })
1639
+ * ```
1640
+ */
1641
+ export declare function sessionStorageAdapter(key?: string): TokenStorage;
1642
+
1495
1643
  /** Internal representation of stored authentication tokens. */
1496
1644
  export declare interface StoredTokens {
1497
1645
  accessToken: string;
@@ -1580,9 +1728,16 @@ declare interface TokenManagerOptions {
1580
1728
  *
1581
1729
  * @example
1582
1730
  * ```ts
1583
- * // Built-in localStorage adapter (browser only)
1731
+ * // Persist across tabs and browser restarts (browser only)
1584
1732
  * import { localStorageAdapter } from '@ledewire/browser'
1585
- * const client = createBrowserClient({ apiKey, storage: localStorageAdapter() })
1733
+ * const lw = init({ apiKey, storage: localStorageAdapter() })
1734
+ * ```
1735
+ *
1736
+ * @example
1737
+ * ```ts
1738
+ * // Persist within the current tab only — cleared on tab close (browser only)
1739
+ * import { sessionStorageAdapter } from '@ledewire/browser'
1740
+ * const lw = init({ apiKey, storage: sessionStorageAdapter() })
1586
1741
  * ```
1587
1742
  */
1588
1743
  export declare interface TokenStorage {