@ledewire/browser 0.5.0 → 0.6.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 CHANGED
@@ -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
@@ -92,7 +92,7 @@ export declare interface AuthPasswordResetResponse {
92
92
  declare type AuthSignupRequest = components['schemas']['AuthSignupRequest'];
93
93
 
94
94
  /**
95
- * Buyer authentication: signup, email/password login, Google OAuth.
95
+ * Buyer authentication: signup, email/password login, Google OAuth, password reset.
96
96
  *
97
97
  * Obtain via `lw.auth` — do not construct directly.
98
98
  *
@@ -130,27 +130,6 @@ declare class BrowserAuthNamespace {
130
130
  * @returns The authentication token response.
131
131
  */
132
132
  loginWithGoogle(body: AuthLoginOAuthRequest): Promise<AuthenticationResponse>;
133
- /**
134
- * Log in using an API key to obtain a seller token.
135
- * Provide only `key` for read-only (`view`) access.
136
- * Provide both `key` and `secret` for read/write (`full`) access.
137
- * Tokens are stored automatically after successful authentication.
138
- *
139
- * Use this before calling `lw.seller.content.*` methods.
140
- *
141
- * @param body - API key credentials.
142
- * @returns The authentication token response.
143
- *
144
- * @example
145
- * ```ts
146
- * // View access (read-only) — sufficient for seller.content.list/search/get
147
- * await lw.auth.loginWithApiKey({ key: 'your_api_key' })
148
- *
149
- * // Full access (read/write)
150
- * await lw.auth.loginWithApiKey({ key: 'your_api_key', secret: 'your_secret' })
151
- * ```
152
- */
153
- loginWithApiKey(body: AuthLoginApiKeyRequest): Promise<AuthenticationResponse>;
154
133
  /**
155
134
  * Request a password reset code to be sent to the buyer's email address.
156
135
  *
@@ -196,12 +175,12 @@ declare class BrowserAuthNamespace {
196
175
  * Instantiate with {@link init} rather than constructing directly.
197
176
  */
198
177
  declare class BrowserClient {
199
- readonly _http: HttpClient;
200
- readonly _tokenManager: TokenManager;
201
- readonly _config: BrowserClientConfig;
178
+ private readonly _http;
179
+ private readonly _tokenManager;
180
+ private readonly _config;
202
181
  /** Platform-level public configuration (no auth required) */
203
182
  readonly config: BrowserConfigNamespace;
204
- /** Buyer authentication: email/password signup/login, Google, password reset */
183
+ /** Buyer authentication: email/password signup/login, Google OAuth, password reset */
205
184
  readonly auth: BrowserAuthNamespace;
206
185
  /** Checkout state machine: determines next action for a piece of content */
207
186
  readonly checkout: CheckoutNamespace;
@@ -211,7 +190,7 @@ declare class BrowserClient {
211
190
  readonly purchases: BrowserPurchasesNamespace;
212
191
  /** Public content with per-user access information */
213
192
  readonly content: BrowserContentNamespace;
214
- /** Seller content: list, search, and get (requires API key view token) */
193
+ /** Seller operations: API key login, content list/search/get */
215
194
  readonly seller: BrowserSellerNamespace;
216
195
  /* Excluded from this release type: __constructor */
217
196
  }
@@ -286,8 +265,10 @@ declare class BrowserConfigNamespace {
286
265
  *
287
266
  * @example
288
267
  * ```ts
289
- * const { content, access } = await lw.content.getWithAccess('content-id')
290
- * 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
+ * }
291
272
  * ```
292
273
  */
293
274
  declare class BrowserContentNamespace {
@@ -298,10 +279,19 @@ declare class BrowserContentNamespace {
298
279
  * authenticated buyer (or a generic access response when unauthenticated).
299
280
  *
300
281
  * @param id - The content ID.
301
- * @param userId - Optional user ID to check a specific user's access status.
302
- * @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
+ * ```
303
293
  */
304
- getWithAccess(id: string, userId?: string): Promise<ContentWithAccessResponse>;
294
+ getWithAccess(id: string): Promise<ContentWithAccessResponse>;
305
295
  }
306
296
 
307
297
  /**
@@ -348,7 +338,7 @@ declare class BrowserPurchasesNamespace {
348
338
  * @example
349
339
  * ```ts
350
340
  * const lw = Ledewire.init({ apiKey: 'your_api_key' })
351
- * await lw.auth.loginWithApiKey({ key: 'your_api_key' })
341
+ * await lw.seller.loginWithApiKey({ key: 'your_api_key' })
352
342
  *
353
343
  * const items = await lw.seller.content.list()
354
344
  * const results = await lw.seller.content.search({ title: 'intro' })
@@ -362,7 +352,8 @@ declare class BrowserSellerContentNamespace {
362
352
  * List all content for the authenticated seller's store.
363
353
  * Requires a token with at least `view` permission.
364
354
  *
365
- * @returns Array of content items.
355
+ * @returns Array of content items. `teaser` is returned as plain text —
356
+ * the SDK decodes base64 automatically.
366
357
  *
367
358
  * @example
368
359
  * ```ts
@@ -376,12 +367,14 @@ declare class BrowserSellerContentNamespace {
376
367
  * Requires a token with at least `view` permission.
377
368
  *
378
369
  * @param body - Search criteria (title, uri, and/or metadata).
379
- * @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.
380
372
  *
381
373
  * @example
382
374
  * ```ts
383
375
  * const results = await lw.seller.content.search({ title: 'intro' })
384
376
  * const byUri = await lw.seller.content.search({ uri: 'vimeo.com' })
377
+ * const byId = await lw.seller.content.search({ external_identifier: 'vimeo:123456789' })
385
378
  * const byMeta = await lw.seller.content.search({ metadata: { author: 'Alice' } })
386
379
  * ```
387
380
  */
@@ -391,11 +384,14 @@ declare class BrowserSellerContentNamespace {
391
384
  * Requires a token with at least `view` permission.
392
385
  *
393
386
  * @param id - The content ID.
394
- * @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()`.
395
390
  *
396
391
  * @example
397
392
  * ```ts
398
393
  * const item = await lw.seller.content.get('content-id')
394
+ * // item.content_body is plain markdown — no atob() needed
399
395
  * ```
400
396
  */
401
397
  get(id: string): Promise<ContentResponse>;
@@ -409,14 +405,39 @@ declare class BrowserSellerContentNamespace {
409
405
  * @example
410
406
  * ```ts
411
407
  * const lw = Ledewire.init({ apiKey: 'your_api_key' })
412
- * 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' })
413
411
  * const items = await lw.seller.content.list()
414
412
  * ```
415
413
  */
416
414
  declare class BrowserSellerNamespace {
415
+ private readonly http;
416
+ private readonly tokenManager;
417
417
  /** Seller content: list, search, and get by API key. */
418
418
  readonly content: BrowserSellerContentNamespace;
419
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>;
420
441
  }
421
442
 
422
443
  /**
@@ -497,16 +518,19 @@ declare class CheckoutNamespace {
497
518
  */
498
519
  export declare type CheckoutNextAction = 'authenticate' | 'fund_wallet' | 'purchase' | 'view_content';
499
520
 
500
- /** Checkout state machine result for a specific content item. */
501
- export declare interface CheckoutState {
502
- content_id: string;
503
- checkout_state: {
504
- is_authenticated: boolean;
505
- has_sufficient_funds: boolean;
506
- has_purchased: boolean;
507
- next_required_action: CheckoutNextAction;
508
- };
509
- }
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;
510
534
 
511
535
  /** Full checkout state for a buyer/content pair, including auth and fund status. */
512
536
  export declare type CheckoutStateResponse = components['schemas']['CheckoutStateResponse'];
@@ -688,7 +712,9 @@ declare interface components {
688
712
  */
689
713
  currency: string;
690
714
  /** @description Additional metadata to include with the payment session */
691
- metadata?: Record<string, never>;
715
+ metadata?: {
716
+ [key: string]: unknown;
717
+ };
692
718
  };
693
719
  WalletPaymentSessionResponse: {
694
720
  /** @description The client secret used by the payment widget to confirm the payment */
@@ -705,7 +731,9 @@ declare interface components {
705
731
  /** @description Type of event (e.g., payment_intent.succeeded, payment_intent.payment_failed) */
706
732
  type?: string;
707
733
  /** @description Event data containing the object that triggered the event */
708
- data?: Record<string, never>;
734
+ data?: {
735
+ [key: string]: unknown;
736
+ };
709
737
  };
710
738
  WalletBalanceResponse: {
711
739
  balance_cents: number;
@@ -1313,7 +1341,7 @@ declare interface components {
1313
1341
  export declare type ContentAccessInfo = components['schemas']['ContentAccessInfo'];
1314
1342
 
1315
1343
  /** Full content item returned by all seller content endpoints. */
1316
- declare type ContentResponse = components['schemas']['ContentResponse'];
1344
+ export declare type ContentResponse = components['schemas']['ContentResponse'];
1317
1345
 
1318
1346
  /** A piece of content with buyer access information. */
1319
1347
  export declare type ContentWithAccessResponse = components['schemas']['ContentWithAccessResponse'];
@@ -1333,6 +1361,25 @@ export declare type ContentWithAccessResponse = components['schemas']['ContentWi
1333
1361
  *
1334
1362
  * @example
1335
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
1336
1383
  * import { ForbiddenError, AuthError } from '@ledewire/node'
1337
1384
  *
1338
1385
  * try {
@@ -1372,15 +1419,16 @@ declare class HttpClient {
1372
1419
  /**
1373
1420
  * GET request with optional query parameters.
1374
1421
  * @param path - API path (e.g. `/v1/wallet/balance`)
1375
- * @param params - Query string parameters
1422
+ * @param params - Query string parameters. `undefined` values are omitted; numbers are coerced to strings.
1376
1423
  */
1377
- get<T>(path: string, params?: Record<string, string>): Promise<T>;
1424
+ get<T>(path: string, params?: Record<string, string | number | undefined>): Promise<T>;
1378
1425
  /**
1379
1426
  * POST request.
1380
1427
  * @param path - API path
1381
1428
  * @param body - Request body (JSON-serialized)
1429
+ * @param params - Optional query string parameters. `undefined` values are omitted; numbers are coerced to strings.
1382
1430
  */
1383
- post<T>(path: string, body?: unknown): Promise<T>;
1431
+ post<T>(path: string, body?: unknown, params?: Record<string, string | number | undefined>): Promise<T>;
1384
1432
  /**
1385
1433
  * PUT request.
1386
1434
  * @param path - API path
@@ -1515,6 +1563,21 @@ export declare class NotFoundError extends LedewireError {
1515
1563
  constructor(message: string, code?: number);
1516
1564
  }
1517
1565
 
1566
+ /**
1567
+ * Pagination parameters accepted by paginated list endpoints.
1568
+ *
1569
+ * Pass as the final argument to any `list()` or `search()` method that
1570
+ * accepts optional pagination. Omitting either field defers to the server
1571
+ * default (page 1, 25 items per page).
1572
+ */
1573
+ export declare interface PaginationParams {
1574
+ /** Page number (1-based). Defaults to 1. */
1575
+ page?: number;
1576
+ /** Items per page. Maximum 100. Defaults to 25. */
1577
+ per_page?: number;
1578
+ [key: string]: number | undefined;
1579
+ }
1580
+
1518
1581
  /**
1519
1582
  * Converts an `expires_at` ISO 8601 string from an API auth response
1520
1583
  * into a Unix timestamp (milliseconds) for use in `StoredTokens.expiresAt`.
@@ -1554,10 +1617,45 @@ export declare interface SellerContentSearchRequest {
1554
1617
  title?: string;
1555
1618
  /** Case-insensitive partial match against the content URI (`external_ref` content only). */
1556
1619
  uri?: string;
1620
+ /**
1621
+ * Exact match against the content's external identifier.
1622
+ * Use the full formatted value as it appears in `ContentResponse.external_identifier`,
1623
+ * e.g. `'vimeo:123456789'`.
1624
+ */
1625
+ external_identifier?: string;
1557
1626
  /** Exact key/value pairs to AND-match against content metadata. */
1558
1627
  metadata?: Record<string, unknown>;
1559
1628
  }
1560
1629
 
1630
+ /**
1631
+ * A {@link TokenStorage} adapter that persists tokens in `sessionStorage`.
1632
+ *
1633
+ * Tokens survive page reloads within the same tab but are cleared when the
1634
+ * tab is closed or the session ends. This prevents token leakage across
1635
+ * tabs and is a good default for sites where users do not expect persistent
1636
+ * sessions (e.g. embedded checkout widgets, kiosk-mode UIs).
1637
+ *
1638
+ * **Security note:** `sessionStorage` is accessible to any JavaScript on the
1639
+ * same origin and tab. It is more isolated than `localStorage` (no
1640
+ * cross-tab sharing), but the default in-memory storage is still safer for
1641
+ * high-security use cases.
1642
+ *
1643
+ * @param key - The `sessionStorage` key used to store tokens.
1644
+ * Defaults to `'lw:tokens'`. Override this if you have multiple
1645
+ * LedeWire integrations on the same origin.
1646
+ *
1647
+ * @example
1648
+ * ```ts
1649
+ * import { init, sessionStorageAdapter } from '@ledewire/browser'
1650
+ *
1651
+ * const lw = init({
1652
+ * apiKey: 'your_api_key',
1653
+ * storage: sessionStorageAdapter(),
1654
+ * })
1655
+ * ```
1656
+ */
1657
+ export declare function sessionStorageAdapter(key?: string): TokenStorage;
1658
+
1561
1659
  /** Internal representation of stored authentication tokens. */
1562
1660
  export declare interface StoredTokens {
1563
1661
  accessToken: string;
@@ -1646,9 +1744,16 @@ declare interface TokenManagerOptions {
1646
1744
  *
1647
1745
  * @example
1648
1746
  * ```ts
1649
- * // Built-in localStorage adapter (browser only)
1747
+ * // Persist across tabs and browser restarts (browser only)
1650
1748
  * import { localStorageAdapter } from '@ledewire/browser'
1651
- * const client = createBrowserClient({ apiKey, storage: localStorageAdapter() })
1749
+ * const lw = init({ apiKey, storage: localStorageAdapter() })
1750
+ * ```
1751
+ *
1752
+ * @example
1753
+ * ```ts
1754
+ * // Persist within the current tab only — cleared on tab close (browser only)
1755
+ * import { sessionStorageAdapter } from '@ledewire/browser'
1756
+ * const lw = init({ apiKey, storage: sessionStorageAdapter() })
1652
1757
  * ```
1653
1758
  */
1654
1759
  export declare interface TokenStorage {