@ledewire/browser 0.3.0 → 0.4.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
@@ -3,7 +3,7 @@
3
3
  [![npm](https://img.shields.io/npm/v/@ledewire/browser)](https://www.npmjs.com/package/@ledewire/browser)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](../../LICENSE)
5
5
 
6
- Browser SDK for the [LedeWire](https://api.ledewire.com/api-docs/index.html) content marketplace — embed buyer authentication, wallet funding, and content purchases on any website with a single script tag.
6
+ Browser SDK for the [LedeWire](https://api.ledewire.com/api-docs/index.html) content marketplace — embed buyer authentication, wallet funding, content purchases, and seller content discovery on any website with a single script tag.
7
7
 
8
8
  ## CDN — No Install Required
9
9
 
@@ -40,14 +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 |
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 |
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) |
51
52
 
52
53
  ## Example: Fetch Google OAuth Client ID Before Sign-In
53
54
 
@@ -92,6 +93,28 @@ switch (checkout_state.next_required_action) {
92
93
  }
93
94
  ```
94
95
 
96
+ ## Example: Seller Content Discovery
97
+
98
+ Use `lw.auth.loginWithApiKey` with only the `key` to obtain a read-only token,
99
+ then browse your store's content catalogue directly from the browser:
100
+
101
+ ```ts
102
+ const lw = Ledewire.init({ apiKey: 'your_api_key' })
103
+
104
+ // Obtain a view-only seller token (key only — no secret needed)
105
+ await lw.auth.loginWithApiKey({ key: 'your_api_key' })
106
+
107
+ // List all content
108
+ const items = await lw.seller.content.list()
109
+
110
+ // Search by title, URI, or metadata
111
+ const results = await lw.seller.content.search({ title: 'intro' })
112
+ const byMeta = await lw.seller.content.search({ metadata: { author: 'Alice' } })
113
+
114
+ // Fetch a single item
115
+ const item = await lw.seller.content.get('content-id')
116
+ ```
117
+
95
118
  ## Token Storage
96
119
 
97
120
  By default tokens are stored **in memory** (most secure — cleared on page unload).
package/dist/index.d.ts CHANGED
@@ -47,6 +47,9 @@ export declare class AuthError extends LedewireError {
47
47
  constructor(message: string, code?: number);
48
48
  }
49
49
 
50
+ /** Request body for API key authentication (seller). */
51
+ declare type AuthLoginApiKeyRequest = components['schemas']['AuthLoginApiKeyRequest'];
52
+
50
53
  /** Request body for buyer email/password login. */
51
54
  declare type AuthLoginEmailRequest = components['schemas']['AuthLoginEmailRequest'];
52
55
 
@@ -95,6 +98,27 @@ declare class BrowserAuthNamespace {
95
98
  * @returns The authentication token response.
96
99
  */
97
100
  loginWithGoogle(body: AuthLoginOAuthRequest): Promise<AuthenticationResponse>;
101
+ /**
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.
106
+ *
107
+ * Use this before calling `lw.seller.content.*` methods.
108
+ *
109
+ * @param body - API key credentials.
110
+ * @returns The authentication token response.
111
+ *
112
+ * @example
113
+ * ```ts
114
+ * // View access (read-only) — sufficient for seller.content.list/search/get
115
+ * await lw.auth.loginWithApiKey({ key: 'your_api_key' })
116
+ *
117
+ * // Full access (read/write)
118
+ * await lw.auth.loginWithApiKey({ key: 'your_api_key', secret: 'your_secret' })
119
+ * ```
120
+ */
121
+ loginWithApiKey(body: AuthLoginApiKeyRequest): Promise<AuthenticationResponse>;
98
122
  private storeTokens;
99
123
  }
100
124
 
@@ -121,6 +145,8 @@ declare class BrowserClient {
121
145
  readonly purchases: BrowserPurchasesNamespace;
122
146
  /** Public content with per-user access information */
123
147
  readonly content: BrowserContentNamespace;
148
+ /** Seller content: list, search, and get (requires API key view token) */
149
+ readonly seller: BrowserSellerNamespace;
124
150
  /* Excluded from this release type: __constructor */
125
151
  }
126
152
 
@@ -248,6 +274,85 @@ declare class BrowserPurchasesNamespace {
248
274
  get(id: string): Promise<PurchaseResponse>;
249
275
  }
250
276
 
277
+ /**
278
+ * Seller content namespace — list, search, and fetch store content by API key.
279
+ *
280
+ * Obtain via `lw.seller.content` — do not construct directly.
281
+ *
282
+ * @example
283
+ * ```ts
284
+ * const lw = Ledewire.init({ apiKey: 'your_api_key' })
285
+ * await lw.auth.loginWithApiKey({ key: 'your_api_key' })
286
+ *
287
+ * const items = await lw.seller.content.list()
288
+ * const results = await lw.seller.content.search({ title: 'intro' })
289
+ * const item = await lw.seller.content.get('content-id')
290
+ * ```
291
+ */
292
+ declare class BrowserSellerContentNamespace {
293
+ private readonly http;
294
+ /* Excluded from this release type: __constructor */
295
+ /**
296
+ * List all content for the authenticated seller's store.
297
+ * Requires a token with at least `view` permission.
298
+ *
299
+ * @returns Array of content items.
300
+ *
301
+ * @example
302
+ * ```ts
303
+ * const items = await lw.seller.content.list()
304
+ * ```
305
+ */
306
+ list(): Promise<ContentResponse[]>;
307
+ /**
308
+ * Search content by title, URI, and/or metadata (AND logic).
309
+ * At least one criterion must be supplied.
310
+ * Requires a token with at least `view` permission.
311
+ *
312
+ * @param body - Search criteria (title, uri, and/or metadata).
313
+ * @returns Array of matching content items.
314
+ *
315
+ * @example
316
+ * ```ts
317
+ * const results = await lw.seller.content.search({ title: 'intro' })
318
+ * const byUri = await lw.seller.content.search({ uri: 'vimeo.com' })
319
+ * const byMeta = await lw.seller.content.search({ metadata: { author: 'Alice' } })
320
+ * ```
321
+ */
322
+ search(body: SellerContentSearchRequest): Promise<ContentResponse[]>;
323
+ /**
324
+ * Get a single content item by ID.
325
+ * Requires a token with at least `view` permission.
326
+ *
327
+ * @param id - The content ID.
328
+ * @returns The content item.
329
+ *
330
+ * @example
331
+ * ```ts
332
+ * const item = await lw.seller.content.get('content-id')
333
+ * ```
334
+ */
335
+ get(id: string): Promise<ContentResponse>;
336
+ }
337
+
338
+ /**
339
+ * Seller operations accessible with a view-permission API key token.
340
+ *
341
+ * Obtain via `lw.seller` — do not construct directly.
342
+ *
343
+ * @example
344
+ * ```ts
345
+ * const lw = Ledewire.init({ apiKey: 'your_api_key' })
346
+ * await lw.auth.loginWithApiKey({ key: 'your_api_key' })
347
+ * const items = await lw.seller.content.list()
348
+ * ```
349
+ */
350
+ declare class BrowserSellerNamespace {
351
+ /** Seller content: list, search, and get by API key. */
352
+ readonly content: BrowserSellerContentNamespace;
353
+ /* Excluded from this release type: __constructor */
354
+ }
355
+
251
356
  /**
252
357
  * Buyer wallet namespace — balance, transactions, and payment session management.
253
358
  *
@@ -455,8 +560,8 @@ declare interface components {
455
560
  refresh_token?: string;
456
561
  };
457
562
  ManageableStore: {
458
- store_id: string;
459
- store_name: string;
563
+ id: string;
564
+ name: string;
460
565
  store_key: string;
461
566
  /**
462
567
  * @description Derived role. `owner` if the user owns this store; `author` if they have author permissions only.
@@ -986,7 +1091,7 @@ declare interface components {
986
1091
  data: components['schemas']['MerchantUser'][];
987
1092
  pagination: components['schemas']['PaginationMeta'];
988
1093
  };
989
- /** @description Lightweight content representation returned by list and search endpoints. Omits body fields to keep list payloads small. Use the detail endpoint (`GET /v1/merchant/:store_id/content/:id`) to retrieve the full body or URI. */
1094
+ /** @description Lightweight content representation returned by list and search endpoints. Omits `content_body` to keep list payloads small. Use the detail endpoint (`GET /v1/merchant/:store_id/content/:id`) to retrieve the full body. */
990
1095
  ContentListItem: {
991
1096
  id: string;
992
1097
  /** @enum {string} */
@@ -1002,6 +1107,8 @@ declare interface components {
1002
1107
  visibility: 'public' | 'unlisted' | 'private';
1003
1108
  /** Format: date-time */
1004
1109
  created_at: string;
1110
+ /** @description External URI for `external_ref` content; optional supplementary link for `markdown` content. Null when not set. */
1111
+ content_uri: string | null;
1005
1112
  /** @description Namespaced platform ID for `external_ref` content. Null for other types. */
1006
1113
  external_identifier?: string | null;
1007
1114
  };
@@ -1139,12 +1246,42 @@ declare interface components {
1139
1246
  /** Checkout state for a specific piece of content and user. */
1140
1247
  export declare type ContentAccessInfo = components['schemas']['ContentAccessInfo'];
1141
1248
 
1249
+ /** Full content item returned by all seller content endpoints. */
1250
+ declare type ContentResponse = components['schemas']['ContentResponse'];
1251
+
1142
1252
  /** A piece of content with buyer access information. */
1143
1253
  export declare type ContentWithAccessResponse = components['schemas']['ContentWithAccessResponse'];
1144
1254
 
1145
1255
  /**
1146
1256
  * Thrown when the authenticated user does not have permission
1147
1257
  * to perform the requested operation.
1258
+ *
1259
+ * **Common cause — merchant login with the wrong account role:**
1260
+ * `POST /v1/auth/merchant/login/email` and `POST /v1/auth/merchant/login/google`
1261
+ * return `403 Forbidden` (not `401`) when the credentials are valid but the
1262
+ * account has no merchant store associations (e.g. a buyer account used on the
1263
+ * merchant endpoint). The `err.message` will be:
1264
+ * `"This account does not have merchant access. Use a merchant or owner account."`
1265
+ *
1266
+ * Use a different account or ask a store owner to add your account as a member.
1267
+ *
1268
+ * @example
1269
+ * ```ts
1270
+ * import { ForbiddenError, AuthError } from '@ledewire/node'
1271
+ *
1272
+ * try {
1273
+ * await client.merchant.auth.loginWithGoogle({ id_token })
1274
+ * } catch (err) {
1275
+ * if (err instanceof ForbiddenError) {
1276
+ * // Credentials were valid but account has no merchant store access.
1277
+ * // err.message → "This account does not have merchant access. Use a merchant or owner account."
1278
+ * console.error('Wrong account role:', err.message)
1279
+ * } else if (err instanceof AuthError) {
1280
+ * // Bad credentials or expired token — re-authenticate.
1281
+ * console.error('Authentication failed:', err.message)
1282
+ * }
1283
+ * }
1284
+ * ```
1148
1285
  */
1149
1286
  export declare class ForbiddenError extends LedewireError {
1150
1287
  constructor(message: string, code?: number);
@@ -1342,6 +1479,19 @@ export declare class PurchaseError extends LedewireError {
1342
1479
  /** A purchase record. */
1343
1480
  export declare type PurchaseResponse = components['schemas']['PurchaseResponse'];
1344
1481
 
1482
+ /**
1483
+ * Search criteria for seller content search.
1484
+ * At least one field must be supplied. All supplied fields must match (AND logic).
1485
+ */
1486
+ export declare interface SellerContentSearchRequest {
1487
+ /** Case-insensitive partial match against the content title. */
1488
+ title?: string;
1489
+ /** Case-insensitive partial match against the content URI (`external_ref` content only). */
1490
+ uri?: string;
1491
+ /** Exact key/value pairs to AND-match against content metadata. */
1492
+ metadata?: Record<string, unknown>;
1493
+ }
1494
+
1345
1495
  /** Internal representation of stored authentication tokens. */
1346
1496
  export declare interface StoredTokens {
1347
1497
  accessToken: string;
@@ -1396,13 +1546,18 @@ declare interface TokenManagerOptions {
1396
1546
  */
1397
1547
  refreshFn: (refreshToken: string) => Promise<StoredTokens>;
1398
1548
  /**
1399
- * Called after a successful token refresh.
1400
- * Use in server environments to persist the new tokens to a database or cache.
1549
+ * Called after a successful background token refresh (not on initial login).
1550
+ * Use for side-effects only — e.g. audit logging, cache invalidation, or
1551
+ * notifying a secondary system when tokens rotate.
1552
+ *
1553
+ * **Do not use this for token persistence.** The `storage` adapter's
1554
+ * `setTokens` is the canonical persistence hook and is already called on
1555
+ * every refresh. Providing both will result in double-writes.
1401
1556
  *
1402
1557
  * @example
1403
1558
  * ```ts
1404
1559
  * onTokenRefreshed: async (tokens) => {
1405
- * await redis.set('session:tokens', JSON.stringify(tokens))
1560
+ * await auditLog.record('token_refreshed', { expiresAt: tokens.expiresAt })
1406
1561
  * }
1407
1562
  * ```
1408
1563
  */
package/dist/index.js CHANGED
@@ -21,23 +21,23 @@ class d extends c {
21
21
  super(e, 403, t), this.name = "ForbiddenError";
22
22
  }
23
23
  }
24
- class k extends c {
24
+ class g extends c {
25
25
  constructor(e, t) {
26
26
  super(e, 404, t), this.name = "NotFoundError";
27
27
  }
28
28
  }
29
- class N extends c {
29
+ class $ extends c {
30
30
  constructor(e, t, r) {
31
31
  super(e, t, r), this.name = "PurchaseError";
32
32
  }
33
33
  }
34
- const g = "https://api.ledewire.com";
34
+ const k = "https://api.ledewire.com";
35
35
  class f {
36
36
  constructor(e = {}) {
37
37
  n(this, "baseUrl");
38
38
  n(this, "getAccessToken");
39
39
  n(this, "onUnauthorized");
40
- this.baseUrl = (e.baseUrl ?? g).replace(/\/$/, ""), this.getAccessToken = e.getAccessToken ?? (() => null), this.onUnauthorized = e.onUnauthorized ?? (() => null);
40
+ this.baseUrl = (e.baseUrl ?? k).replace(/\/$/, ""), this.getAccessToken = e.getAccessToken ?? (() => null), this.onUnauthorized = e.onUnauthorized ?? (() => null);
41
41
  }
42
42
  /**
43
43
  * GET request with optional query parameters.
@@ -117,7 +117,7 @@ class f {
117
117
  case 403:
118
118
  throw new d(r, o);
119
119
  case 404:
120
- throw new k(r, o);
120
+ throw new g(r, o);
121
121
  default:
122
122
  throw new c(r, e.status, o);
123
123
  }
@@ -226,6 +226,30 @@ class E {
226
226
  const t = await this.http.post("/v1/auth/login/google", e);
227
227
  return await this.storeTokens(t), t;
228
228
  }
229
+ /**
230
+ * Log in using an API key to obtain a seller token.
231
+ * Provide only `key` for read-only (`view`) access.
232
+ * Provide both `key` and `secret` for read/write (`full`) access.
233
+ * Tokens are stored automatically after successful authentication.
234
+ *
235
+ * Use this before calling `lw.seller.content.*` methods.
236
+ *
237
+ * @param body - API key credentials.
238
+ * @returns The authentication token response.
239
+ *
240
+ * @example
241
+ * ```ts
242
+ * // View access (read-only) — sufficient for seller.content.list/search/get
243
+ * await lw.auth.loginWithApiKey({ key: 'your_api_key' })
244
+ *
245
+ * // Full access (read/write)
246
+ * await lw.auth.loginWithApiKey({ key: 'your_api_key', secret: 'your_secret' })
247
+ * ```
248
+ */
249
+ async loginWithApiKey(e) {
250
+ const t = await this.http.post("/v1/auth/login/api-key", e);
251
+ return await this.storeTokens(t), t;
252
+ }
229
253
  async storeTokens(e) {
230
254
  await this.tokenManager.setTokens({
231
255
  accessToken: e.access_token,
@@ -257,7 +281,7 @@ class A {
257
281
  return this.http.get("/v1/config/public");
258
282
  }
259
283
  }
260
- class x {
284
+ class v {
261
285
  constructor(e) {
262
286
  this.http = e;
263
287
  }
@@ -277,7 +301,7 @@ class x {
277
301
  );
278
302
  }
279
303
  }
280
- class U {
304
+ class x {
281
305
  constructor(e) {
282
306
  this.http = e;
283
307
  }
@@ -308,6 +332,67 @@ class U {
308
332
  return this.http.get(`/v1/purchases/${e}`);
309
333
  }
310
334
  }
335
+ class U {
336
+ /** @internal */
337
+ constructor(e) {
338
+ this.http = e;
339
+ }
340
+ /**
341
+ * List all content for the authenticated seller's store.
342
+ * Requires a token with at least `view` permission.
343
+ *
344
+ * @returns Array of content items.
345
+ *
346
+ * @example
347
+ * ```ts
348
+ * const items = await lw.seller.content.list()
349
+ * ```
350
+ */
351
+ async list() {
352
+ return this.http.get("/v1/seller/content");
353
+ }
354
+ /**
355
+ * Search content by title, URI, and/or metadata (AND logic).
356
+ * At least one criterion must be supplied.
357
+ * Requires a token with at least `view` permission.
358
+ *
359
+ * @param body - Search criteria (title, uri, and/or metadata).
360
+ * @returns Array of matching content items.
361
+ *
362
+ * @example
363
+ * ```ts
364
+ * const results = await lw.seller.content.search({ title: 'intro' })
365
+ * const byUri = await lw.seller.content.search({ uri: 'vimeo.com' })
366
+ * const byMeta = await lw.seller.content.search({ metadata: { author: 'Alice' } })
367
+ * ```
368
+ */
369
+ async search(e) {
370
+ return this.http.post("/v1/seller/content/search", e);
371
+ }
372
+ /**
373
+ * Get a single content item by ID.
374
+ * Requires a token with at least `view` permission.
375
+ *
376
+ * @param id - The content ID.
377
+ * @returns The content item.
378
+ *
379
+ * @example
380
+ * ```ts
381
+ * const item = await lw.seller.content.get('content-id')
382
+ * ```
383
+ */
384
+ async get(e) {
385
+ return this.http.get(`/v1/seller/content/${e}`);
386
+ }
387
+ }
388
+ class S {
389
+ /** @internal */
390
+ constructor(e) {
391
+ /** Seller content: list, search, and get by API key. */
392
+ n(this, "content");
393
+ this.content = new U(e);
394
+ }
395
+ }
311
396
  class b {
312
397
  constructor(e) {
313
398
  this.http = e;
@@ -347,7 +432,7 @@ class b {
347
432
  return this.http.get(`/v1/wallet/payment-status/${e}`);
348
433
  }
349
434
  }
350
- class v {
435
+ class P {
351
436
  constructor(e) {
352
437
  this.http = e;
353
438
  }
@@ -366,7 +451,7 @@ class v {
366
451
  return this.http.get(`/v1/checkout/state/${e}`);
367
452
  }
368
453
  }
369
- function O(s) {
454
+ function C(s) {
370
455
  const e = s.baseUrl ?? "https://api.ledewire.com", t = s.storage ?? new m(), r = new y({
371
456
  storage: t,
372
457
  refreshFn: async (h) => {
@@ -389,9 +474,9 @@ function O(s) {
389
474
  getAccessToken: () => r.getAccessToken(),
390
475
  onUnauthorized: () => r.handleUnauthorized()
391
476
  });
392
- return new S(o, r, s);
477
+ return new N(o, r, s);
393
478
  }
394
- class S {
479
+ class N {
395
480
  /** @internal */
396
481
  constructor(e, t, r) {
397
482
  /** Platform-level public configuration (no auth required) */
@@ -406,11 +491,13 @@ class S {
406
491
  n(this, "purchases");
407
492
  /** Public content with per-user access information */
408
493
  n(this, "content");
409
- this._http = e, this._tokenManager = t, this._config = r, this.config = new A(e), this.auth = new E(e, t), this.checkout = new v(e), this.wallet = new b(e), this.purchases = new U(e), this.content = new x(e);
494
+ /** Seller content: list, search, and get (requires API key view token) */
495
+ n(this, "seller");
496
+ this._http = e, this._tokenManager = t, this._config = r, this.config = new A(e), this.auth = new E(e, t), this.checkout = new P(e), this.wallet = new b(e), this.purchases = new x(e), this.content = new v(e), this.seller = new S(e);
410
497
  }
411
498
  }
412
- const P = "lw:tokens";
413
- function _(s = P) {
499
+ const R = "lw:tokens";
500
+ function B(s = R) {
414
501
  return {
415
502
  getTokens() {
416
503
  const e = localStorage.getItem(s);
@@ -434,10 +521,10 @@ export {
434
521
  d as ForbiddenError,
435
522
  c as LedewireError,
436
523
  m as MemoryTokenStorage,
437
- k as NotFoundError,
438
- N as PurchaseError,
439
- O as init,
440
- _ as localStorageAdapter,
524
+ g as NotFoundError,
525
+ $ as PurchaseError,
526
+ C as init,
527
+ B as localStorageAdapter,
441
528
  l as parseExpiresAt
442
529
  };
443
530
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../core/src/errors.ts","../../core/src/http-client.ts","../../core/src/token-manager.ts","../src/resources/auth.ts","../src/resources/config.ts","../src/resources/content.ts","../src/resources/purchases.ts","../src/resources/wallet.ts","../src/resources/checkout.ts","../src/client.ts","../src/local-storage-adapter.ts"],"sourcesContent":["/**\n * Base error class for all LedeWire SDK errors.\n * All errors thrown by the SDK are instances of this class,\n * making it safe to use `err instanceof LedewireError` as a type guard.\n *\n * @example\n * ```ts\n * try {\n * await client.purchases.create({ contentId: '...' })\n * } catch (err) {\n * if (err instanceof LedewireError) {\n * console.error(err.statusCode, err.message)\n * }\n * }\n * ```\n */\nexport class LedewireError extends Error {\n /** HTTP status code returned by the API (e.g. 400, 401, 404, 422). */\n public readonly statusCode: number\n\n /** Machine-readable error code from the API error body, if present. */\n public readonly code: number | undefined\n\n constructor(message: string, statusCode: number, code?: number) {\n super(message)\n this.name = 'LedewireError'\n this.statusCode = statusCode\n this.code = code\n // Restore prototype chain (required for extending built-in Error in TS)\n Object.setPrototypeOf(this, new.target.prototype)\n }\n}\n\n/**\n * Thrown when the request lacks valid authentication credentials,\n * or when a token refresh fails and re-authentication is required.\n */\nexport class AuthError extends LedewireError {\n constructor(message: string, code?: number) {\n super(message, 401, code)\n this.name = 'AuthError'\n }\n}\n\n/**\n * Thrown when the authenticated user does not have permission\n * to perform the requested operation.\n */\nexport class ForbiddenError extends LedewireError {\n constructor(message: string, code?: number) {\n super(message, 403, code)\n this.name = 'ForbiddenError'\n }\n}\n\n/**\n * Thrown when the requested resource does not exist.\n */\nexport class NotFoundError extends LedewireError {\n constructor(message: string, code?: number) {\n super(message, 404, code)\n this.name = 'NotFoundError'\n }\n}\n\n/**\n * Thrown when the purchase cannot be completed due to a validation\n * failure, such as a price mismatch or a duplicate purchase.\n */\nexport class PurchaseError extends LedewireError {\n constructor(message: string, statusCode: number, code?: number) {\n super(message, statusCode, code)\n this.name = 'PurchaseError'\n }\n}\n","import type { components } from './api.gen.js'\nimport { AuthError, ForbiddenError, LedewireError, NotFoundError } from './errors.js'\n\n/** @internal */\ntype ApiErrorBody = components['schemas']['ErrorResponse']\n\n/**\n * Configuration options for `HttpClient`.\n */\nexport interface HttpClientConfig {\n /** API base URL. Defaults to the production API. */\n baseUrl?: string\n /**\n * Returns the current access token (or null) before each request.\n * If null, requests are sent without an Authorization header.\n */\n getAccessToken?: () => string | null | Promise<string | null>\n /**\n * Called when a 401 is received on the first attempt.\n * Should refresh and return the new access token, or null to propagate\n * an `AuthError` to the caller.\n */\n onUnauthorized?: () => string | null | Promise<string | null>\n}\n\nconst DEFAULT_BASE_URL = 'https://api.ledewire.com'\n\n/**\n * Core HTTP client used by all SDK packages.\n *\n * Features:\n * - Injects `Authorization: Bearer <token>` headers automatically\n * - Maps HTTP error responses to typed `LedewireError` subclasses\n * - On receiving a 401, calls `onUnauthorized` once and retries the request\n *\n * This is an internal class - consumers should use the\n * package-level client factories (`init` / `createClient`) instead.\n */\nexport class HttpClient {\n private readonly baseUrl: string\n private readonly getAccessToken: NonNullable<HttpClientConfig['getAccessToken']>\n private readonly onUnauthorized: NonNullable<HttpClientConfig['onUnauthorized']>\n\n constructor(config: HttpClientConfig = {}) {\n this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, '')\n this.getAccessToken = config.getAccessToken ?? (() => null)\n this.onUnauthorized = config.onUnauthorized ?? (() => null)\n }\n\n /**\n * GET request with optional query parameters.\n * @param path - API path (e.g. `/v1/wallet/balance`)\n * @param params - Query string parameters\n */\n async get<T>(path: string, params?: Record<string, string>): Promise<T> {\n return this.request<T>('GET', this.buildUrl(path, params))\n }\n\n /**\n * POST request.\n * @param path - API path\n * @param body - Request body (JSON-serialized)\n */\n async post<T>(path: string, body?: unknown): Promise<T> {\n return this.request<T>('POST', this.buildUrl(path), body)\n }\n\n /**\n * PUT request.\n * @param path - API path\n * @param body - Request body (JSON-serialized)\n */\n async put<T>(path: string, body?: unknown): Promise<T> {\n return this.request<T>('PUT', this.buildUrl(path), body)\n }\n\n /**\n * PATCH request.\n * @param path - API path\n * @param body - Partial update body (JSON-serialized)\n */\n async patch<T>(path: string, body?: unknown): Promise<T> {\n return this.request<T>('PATCH', this.buildUrl(path), body)\n }\n\n /**\n * DELETE request.\n * @param path - API path\n */\n async delete<T = void>(path: string): Promise<T> {\n return this.request<T>('DELETE', this.buildUrl(path))\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private buildUrl(path: string, params?: Record<string, string>): string {\n const url = `${this.baseUrl}${path}`\n if (!params || Object.keys(params).length === 0) return url\n return `${url}?${new URLSearchParams(params).toString()}`\n }\n\n private async request<T>(\n method: string,\n url: string,\n body?: unknown,\n isRetry = false,\n ): Promise<T> {\n const token = await this.getAccessToken()\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n }\n if (token) headers['Authorization'] = `Bearer ${token}`\n\n const response = await fetch(url, {\n method,\n headers,\n ...(body !== undefined && { body: JSON.stringify(body) }),\n })\n\n if (response.status === 401 && !isRetry) {\n const newToken = await this.onUnauthorized()\n if (newToken) {\n return this.request<T>(method, url, body, true)\n }\n throw new AuthError('Session expired. Please re-authenticate.')\n }\n\n if (!response.ok) {\n await this.throwApiError(response)\n }\n\n if (response.status === 204) {\n return undefined as T\n }\n\n return response.json() as Promise<T>\n }\n\n private async throwApiError(response: Response): Promise<never> {\n let body: ApiErrorBody | undefined\n try {\n body = (await response.json()) as ApiErrorBody\n } catch {\n // Non-JSON body - fall through to use statusText\n }\n\n const message = body?.error.message ?? response.statusText\n const code = body?.error.code\n\n switch (response.status) {\n case 401:\n throw new AuthError(message, code)\n case 403:\n throw new ForbiddenError(message, code)\n case 404:\n throw new NotFoundError(message, code)\n default:\n throw new LedewireError(message, response.status, code)\n }\n }\n}\n","import type { StoredTokens, TokenStorage } from './types.js'\nimport { AuthError } from './errors.js'\n\n/** How many milliseconds before expiry to proactively refresh the token. */\nconst REFRESH_THRESHOLD_MS = 60_000 // 60 seconds\n\n/**\n * Options for constructing a `TokenManager`.\n */\nexport interface TokenManagerOptions {\n storage: TokenStorage\n /**\n * Exchanges a refresh token for a new token pair.\n * Implemented by each package's client factory using the\n * `/v1/auth/token/refresh` endpoint.\n */\n refreshFn: (refreshToken: string) => Promise<StoredTokens>\n /**\n * Called after a successful token refresh.\n * Use in server environments to persist the new tokens to a database or cache.\n *\n * @example\n * ```ts\n * onTokenRefreshed: async (tokens) => {\n * await redis.set('session:tokens', JSON.stringify(tokens))\n * }\n * ```\n */\n onTokenRefreshed?: (tokens: StoredTokens) => void | Promise<void>\n /**\n * Called when a refresh attempt fails and the user must re-authenticate.\n * Typically used to redirect to login or emit a UI event.\n *\n * @example\n * ```ts\n * onAuthExpired: () => { window.location.href = '/login' }\n * ```\n */\n onAuthExpired?: () => void | Promise<void>\n}\n\n/**\n * Manages the JWT token lifecycle transparently.\n *\n * - Proactively refreshes the access token when it expires within 60 seconds\n * - Handles reactive refresh on 401 responses from the HTTP client\n * - Deduplicates concurrent refresh calls (only one in-flight refresh at a time)\n * - Fires `onAuthExpired` when refresh fails so the UI can prompt re-login\n *\n * This is an internal class - use the package-level client factories instead.\n */\nexport class TokenManager {\n private readonly storage: TokenStorage\n private readonly refreshFn: TokenManagerOptions['refreshFn']\n private readonly onTokenRefreshed: TokenManagerOptions['onTokenRefreshed']\n private readonly onAuthExpired: TokenManagerOptions['onAuthExpired']\n private refreshPromise: Promise<StoredTokens | null> | null = null\n\n constructor(options: TokenManagerOptions) {\n this.storage = options.storage\n this.refreshFn = options.refreshFn\n this.onTokenRefreshed = options.onTokenRefreshed\n this.onAuthExpired = options.onAuthExpired\n }\n\n /**\n * Returns a valid access token, refreshing proactively if needed.\n * Returns `null` if no tokens are stored (user not authenticated).\n */\n async getAccessToken(): Promise<string | null> {\n const stored = await this.storage.getTokens()\n if (!stored) return null\n\n const expiresInMs = stored.expiresAt - Date.now()\n if (expiresInMs < REFRESH_THRESHOLD_MS) {\n const refreshed = await this.performRefresh(stored.refreshToken)\n return refreshed?.accessToken ?? null\n }\n\n return stored.accessToken\n }\n\n /**\n * Called by `HttpClient` when a 401 is received.\n * Attempts one refresh; returns the new access token or null.\n */\n async handleUnauthorized(): Promise<string | null> {\n const stored = await this.storage.getTokens()\n if (!stored) {\n await this.onAuthExpired?.()\n return null\n }\n const refreshed = await this.performRefresh(stored.refreshToken)\n if (!refreshed) await this.onAuthExpired?.()\n return refreshed?.accessToken ?? null\n }\n\n /** Store new tokens after a successful login or signup. */\n async setTokens(tokens: StoredTokens): Promise<void> {\n await this.storage.setTokens(tokens)\n }\n\n /** Clear all stored tokens (call on logout). */\n async clearTokens(): Promise<void> {\n await this.storage.clearTokens()\n }\n\n // ---------------------------------------------------------------------------\n // Private\n // ---------------------------------------------------------------------------\n\n private performRefresh(refreshToken: string): Promise<StoredTokens | null> {\n if (this.refreshPromise) return this.refreshPromise\n\n this.refreshPromise = this.refreshFn(refreshToken)\n .then(async (tokens) => {\n await this.storage.setTokens(tokens)\n await this.onTokenRefreshed?.(tokens)\n return tokens\n })\n .catch((_err: unknown) => {\n void this.storage.clearTokens()\n throw new AuthError('Token refresh failed. Please re-authenticate.')\n })\n .finally(() => {\n this.refreshPromise = null\n })\n\n return this.refreshPromise\n }\n}\n\n// ---------------------------------------------------------------------------\n// Built-in storage adapters\n// ---------------------------------------------------------------------------\n\n/**\n * In-memory token storage (default for both packages).\n * Tokens are cleared when the page unloads or the process exits.\n * This is the most secure default for browser environments.\n */\nexport class MemoryTokenStorage implements TokenStorage {\n private tokens: StoredTokens | null = null\n\n getTokens(): StoredTokens | null {\n return this.tokens\n }\n\n setTokens(tokens: StoredTokens): void {\n this.tokens = tokens\n }\n\n clearTokens(): void {\n this.tokens = null\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Converts an `expires_at` ISO 8601 string from an API auth response\n * into a Unix timestamp (milliseconds) for use in `StoredTokens.expiresAt`.\n *\n * @param expiresAt - ISO 8601 datetime string, e.g. `\"2026-01-01T12:00:00Z\"`\n * @returns Unix timestamp in milliseconds\n */\nexport function parseExpiresAt(expiresAt: string): number {\n return new Date(expiresAt).getTime()\n}\n","/**\n * Buyer authentication namespace for browser environments.\n *\n * Tokens are stored automatically in the `TokenManager` after every\n * successful call — callers never need to manage tokens directly.\n *\n * @module\n */\nimport { parseExpiresAt } from '@ledewire/core'\nimport type { HttpClient, TokenManager } from '@ledewire/core'\nimport type {\n AuthLoginEmailRequest,\n AuthLoginOAuthRequest,\n AuthSignupRequest,\n AuthenticationResponse,\n} from '@ledewire/core'\n\n/**\n * Buyer authentication: signup, email/password login, Google OAuth.\n *\n * Obtain via `lw.auth` — do not construct directly.\n *\n * @example\n * ```ts\n * const lw = Ledewire.init({ apiKey: 'your_api_key' })\n * await lw.auth.loginWithEmail({ email: 'u@example.com', password: 'pw' })\n * ```\n */\nexport class BrowserAuthNamespace {\n /** @internal */\n constructor(\n private readonly http: HttpClient,\n private readonly tokenManager: TokenManager,\n ) {}\n\n /**\n * Register a new buyer account with email and password.\n * Tokens are stored automatically after successful signup.\n *\n * @param body - Signup credentials and display name.\n * @returns The authentication token response.\n */\n async signup(body: AuthSignupRequest): Promise<AuthenticationResponse> {\n const res = await this.http.post<AuthenticationResponse>('/v1/auth/signup', body)\n await this.storeTokens(res)\n return res\n }\n\n /**\n * Log in with email and password.\n * Tokens are stored automatically after successful login.\n *\n * @param body - Email and password credentials.\n * @returns The authentication token response.\n */\n async loginWithEmail(body: AuthLoginEmailRequest): Promise<AuthenticationResponse> {\n const res = await this.http.post<AuthenticationResponse>('/v1/auth/login/email', body)\n await this.storeTokens(res)\n return res\n }\n\n /**\n * Log in with a Google ID token obtained from the Google OAuth flow.\n * Tokens are stored automatically after successful login.\n *\n * @param body - The Google ID token.\n * @returns The authentication token response.\n */\n async loginWithGoogle(body: AuthLoginOAuthRequest): Promise<AuthenticationResponse> {\n const res = await this.http.post<AuthenticationResponse>('/v1/auth/login/google', body)\n await this.storeTokens(res)\n return res\n }\n\n private async storeTokens(res: AuthenticationResponse): Promise<void> {\n await this.tokenManager.setTokens({\n accessToken: res.access_token,\n refreshToken: res.refresh_token,\n expiresAt: parseExpiresAt(res.expires_at),\n })\n }\n}\n","/**\n * Platform configuration namespace (browser).\n *\n * Provides access to public (unauthenticated) platform-level configuration.\n *\n * @module\n */\nimport type { HttpClient } from '@ledewire/core'\nimport type { PublicConfigResponse } from '@ledewire/core'\n\n/**\n * Platform-level configuration for browser clients.\n *\n * Obtain via `client.config` — do not construct directly.\n */\nexport class BrowserConfigNamespace {\n /** @internal */\n constructor(protected readonly http: HttpClient) {}\n\n /**\n * Returns platform-level public configuration.\n * No authentication required — safe to call before the user has signed in.\n *\n * Use `google_client_id` to initialise the Google Identity Services library\n * on the storefront login page without requiring a prior authenticated call.\n *\n * @example\n * ```ts\n * const lw = init({ apiKey: 'your_api_key' })\n * const { google_client_id } = await lw.config.getPublic()\n * google.accounts.id.initialize({ client_id: google_client_id, callback })\n * ```\n */\n async getPublic(): Promise<PublicConfigResponse> {\n return this.http.get<PublicConfigResponse>('/v1/config/public')\n }\n}\n","import type { HttpClient } from '@ledewire/core'\nimport type { ContentWithAccessResponse } from '@ledewire/core'\n\n/**\n * Buyer content namespace — fetch content with real-time access information.\n *\n * Obtain via `lw.content` — do not construct directly.\n *\n * @example\n * ```ts\n * const { content, access } = await lw.content.getWithAccess('content-id')\n * if (access.has_access) showFullContent(content)\n * ```\n */\nexport class BrowserContentNamespace {\n constructor(protected readonly http: HttpClient) {}\n\n /**\n * Returns a content item together with access information for the\n * authenticated buyer (or a generic access response when unauthenticated).\n *\n * @param id - The content ID.\n * @param userId - Optional user ID to check a specific user's access status.\n * @returns The content item with access information.\n */\n async getWithAccess(id: string, userId?: string): Promise<ContentWithAccessResponse> {\n const params: Record<string, string> = {}\n if (userId !== undefined) {\n params['user_id'] = userId\n }\n return this.http.get<ContentWithAccessResponse>(\n `/v1/content/${id}/with-access`,\n Object.keys(params).length > 0 ? params : undefined,\n )\n }\n}\n","import type { HttpClient } from '@ledewire/core'\nimport type { PurchaseCreateRequest, PurchaseResponse } from '@ledewire/core'\n\n/**\n * Buyer purchases namespace — create and retrieve content purchases.\n *\n * Obtain via `lw.purchases` — do not construct directly.\n *\n * @example\n * ```ts\n * await lw.purchases.create({ content_id: 'content-id' })\n * const all = await lw.purchases.list()\n * ```\n */\nexport class BrowserPurchasesNamespace {\n constructor(protected readonly http: HttpClient) {}\n\n /**\n * Completes a content purchase using the buyer's wallet balance.\n *\n * @param body - The content ID and expected price in cents.\n * @returns The completed purchase record.\n */\n async create(body: PurchaseCreateRequest): Promise<PurchaseResponse> {\n return this.http.post<PurchaseResponse>('/v1/purchases', body)\n }\n\n /**\n * Returns all purchases made by the authenticated buyer.\n *\n * @returns A list of purchase records, newest first.\n */\n async list(): Promise<PurchaseResponse[]> {\n return this.http.get<PurchaseResponse[]>('/v1/purchases')\n }\n\n /**\n * Returns a single purchase by ID.\n *\n * @param id - The purchase ID.\n * @returns The purchase record.\n */\n async get(id: string): Promise<PurchaseResponse> {\n return this.http.get<PurchaseResponse>(`/v1/purchases/${id}`)\n }\n}\n","import type { HttpClient } from '@ledewire/core'\nimport type {\n WalletBalanceResponse,\n WalletPaymentSessionRequest,\n WalletPaymentSessionResponse,\n WalletPaymentStatusResponse,\n WalletTransactionItem,\n} from '@ledewire/core'\n\n/**\n * Buyer wallet namespace — balance, transactions, and payment session management.\n *\n * Obtain via `lw.wallet` — do not construct directly.\n *\n * @example\n * ```ts\n * const { balance_cents } = await lw.wallet.balance()\n * const session = await lw.wallet.createPaymentSession({ amount_cents: 500 })\n * ```\n */\nexport class BrowserWalletNamespace {\n constructor(protected readonly http: HttpClient) {}\n\n /**\n * Returns the authenticated buyer's current wallet balance.\n *\n * @returns The current wallet balance in cents.\n */\n async balance(): Promise<WalletBalanceResponse> {\n return this.http.get<WalletBalanceResponse>('/v1/wallet/balance')\n }\n\n /**\n * Returns the authenticated buyer's wallet transaction history, newest first.\n *\n * @returns A list of completed wallet transaction entries.\n */\n async transactions(): Promise<WalletTransactionItem[]> {\n return this.http.get<WalletTransactionItem[]>('/v1/wallet/transactions')\n }\n\n /**\n * Creates a payment session for funding the buyer's wallet.\n *\n * @param body - The amount and currency to fund.\n * @returns Payment session details for use with the payment provider widget.\n */\n async createPaymentSession(\n body: WalletPaymentSessionRequest,\n ): Promise<WalletPaymentSessionResponse> {\n return this.http.post<WalletPaymentSessionResponse>('/v1/wallet/payment-session', body)\n }\n\n /**\n * Polls the status of a wallet funding payment session.\n *\n * @param sessionId - The session ID returned by `createPaymentSession`.\n * @returns The current payment status.\n */\n async getPaymentStatus(sessionId: string): Promise<WalletPaymentStatusResponse> {\n return this.http.get<WalletPaymentStatusResponse>(`/v1/wallet/payment-status/${sessionId}`)\n }\n}\n","import type { HttpClient } from '@ledewire/core'\nimport type { CheckoutStateResponse } from '@ledewire/core'\n\n/**\n * Checkout namespace — buyer checkout state for a specific content item.\n *\n * Obtain via `lw.checkout` — do not construct directly.\n *\n * @example\n * ```ts\n * const state = await lw.checkout.state('content-id')\n * // state.checkout_state.next_required_action:\n * // 'authenticate' | 'fund_wallet' | 'purchase' | 'view_content'\n * ```\n */\nexport class CheckoutNamespace {\n constructor(protected readonly http: HttpClient) {}\n\n /**\n * Returns the authenticated buyer's checkout readiness for a given piece of\n * content. Indicates whether the buyer needs to authenticate, fund their\n * wallet, or purchase before accessing the content.\n *\n * Works without authentication — returns anonymous checkout state when the\n * buyer is not logged in.\n *\n * @param contentId - The content item ID.\n * @returns The checkout state, including what action is required next.\n */\n async state(contentId: string): Promise<CheckoutStateResponse> {\n return this.http.get<CheckoutStateResponse>(`/v1/checkout/state/${contentId}`)\n }\n}\n","import { HttpClient, MemoryTokenStorage, TokenManager, parseExpiresAt } from '@ledewire/core'\nimport type { TokenStorage } from '@ledewire/core'\nimport { BrowserAuthNamespace } from './resources/auth.js'\nimport { BrowserConfigNamespace } from './resources/config.js'\nimport { BrowserContentNamespace } from './resources/content.js'\nimport { BrowserPurchasesNamespace } from './resources/purchases.js'\nimport { BrowserWalletNamespace } from './resources/wallet.js'\nimport { CheckoutNamespace } from './resources/checkout.js'\n\n/**\n * Configuration options for the LedeWire browser client.\n */\nexport interface BrowserClientConfig {\n /**\n * LedeWire API key that identifies the store.\n * Obtained from the LedeWire merchant dashboard.\n */\n apiKey: string\n /**\n * Override the API base URL.\n * Defaults to `https://api.ledewire.com`.\n */\n baseUrl?: string\n /**\n * Token storage adapter.\n * Defaults to {@link MemoryTokenStorage} (in-memory, most secure).\n *\n * To persist sessions across page reloads, use the built-in\n * `localStorageAdapter`:\n * ```ts\n * import { init, localStorageAdapter } from '@ledewire/browser'\n * const lw = init({ apiKey: '...', storage: localStorageAdapter() })\n * ```\n */\n storage?: TokenStorage\n /**\n * Called when the user's session expires and cannot be refreshed.\n * Use this to show a re-authentication prompt.\n *\n * @example\n * ```ts\n * onAuthExpired: () => showLoginModal()\n * ```\n */\n onAuthExpired?: () => void\n}\n\n/**\n * Initialises a LedeWire browser client.\n * This is the primary entry point for the browser SDK.\n *\n * @example CDN usage:\n * ```html\n * <script src=\"https://cdn.jsdelivr.net/npm/@ledewire/browser@1/dist/ledewire.min.js\"></script>\n * <script>\n * const lw = Ledewire.init({ apiKey: 'your_api_key' })\n * const state = await lw.checkout.state('content-id')\n * </script>\n * ```\n *\n * @example npm usage:\n * ```ts\n * import { init } from '@ledewire/browser'\n * const lw = init({ apiKey: 'your_api_key' })\n * ```\n */\nexport function init(config: BrowserClientConfig): BrowserClient {\n const baseUrl = config.baseUrl ?? 'https://api.ledewire.com'\n const storage = config.storage ?? new MemoryTokenStorage()\n\n const tokenManager = new TokenManager({\n storage,\n refreshFn: async (refreshToken) => {\n const res = await fetch(`${baseUrl}/v1/auth/token/refresh`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ refresh_token: refreshToken }),\n })\n if (!res.ok) throw new Error('Token refresh failed')\n const data = (await res.json()) as {\n access_token: string\n refresh_token: string\n expires_at: string\n }\n return {\n accessToken: data.access_token,\n refreshToken: data.refresh_token,\n expiresAt: parseExpiresAt(data.expires_at),\n }\n },\n ...(config.onAuthExpired !== undefined && { onAuthExpired: config.onAuthExpired }),\n })\n\n const http = new HttpClient({\n baseUrl,\n getAccessToken: () => tokenManager.getAccessToken(),\n onUnauthorized: () => tokenManager.handleUnauthorized(),\n })\n\n return new BrowserClient(http, tokenManager, config)\n}\n\n/**\n * The LedeWire browser client.\n * Access buyer flows through the namespaced properties.\n *\n * @remarks\n * Instantiate with {@link init} rather than constructing directly.\n */\nexport class BrowserClient {\n /** Platform-level public configuration (no auth required) */\n readonly config: BrowserConfigNamespace\n\n /** Buyer authentication: email/password signup/login, Google, password reset */\n readonly auth: BrowserAuthNamespace\n\n /** Checkout state machine: determines next action for a piece of content */\n readonly checkout: CheckoutNamespace\n\n /** Wallet: balance, fund via payment session, transaction history */\n readonly wallet: BrowserWalletNamespace\n\n /** Content purchases for the authenticated buyer */\n readonly purchases: BrowserPurchasesNamespace\n\n /** Public content with per-user access information */\n readonly content: BrowserContentNamespace\n\n /** @internal */\n constructor(\n public readonly _http: HttpClient,\n public readonly _tokenManager: TokenManager,\n public readonly _config: BrowserClientConfig,\n ) {\n this.config = new BrowserConfigNamespace(_http)\n this.auth = new BrowserAuthNamespace(_http, _tokenManager)\n this.checkout = new CheckoutNamespace(_http)\n this.wallet = new BrowserWalletNamespace(_http)\n this.purchases = new BrowserPurchasesNamespace(_http)\n this.content = new BrowserContentNamespace(_http)\n }\n}\n","import type { TokenStorage, StoredTokens } from '@ledewire/core'\n\nconst DEFAULT_STORAGE_KEY = 'lw:tokens'\n\n/**\n * A {@link TokenStorage} adapter that persists tokens in `localStorage`.\n *\n * Tokens survive page reloads and browser restarts, which is convenient\n * for sites where users expect persistent sessions.\n *\n * **Security note:** `localStorage` is accessible to any JavaScript on the\n * same origin. Only use this adapter on sites you fully control and trust.\n * The default in-memory storage is safer for high-security use cases.\n *\n * @param key - The `localStorage` key used to store tokens.\n * Defaults to `'lw:tokens'`. Override this if you have multiple\n * LedeWire integrations on the same origin.\n *\n * @example\n * ```ts\n * import { init, localStorageAdapter } from '@ledewire/browser'\n *\n * const lw = init({\n * apiKey: 'your_api_key',\n * storage: localStorageAdapter(),\n * })\n * ```\n */\nexport function localStorageAdapter(key = DEFAULT_STORAGE_KEY): TokenStorage {\n return {\n getTokens(): StoredTokens | null {\n const raw = localStorage.getItem(key)\n if (!raw) return null\n try {\n return JSON.parse(raw) as StoredTokens\n } catch {\n // Corrupted value - treat as empty\n return null\n }\n },\n setTokens(tokens: StoredTokens): void {\n localStorage.setItem(key, JSON.stringify(tokens))\n },\n clearTokens(): void {\n localStorage.removeItem(key)\n },\n }\n}\n"],"names":["LedewireError","message","statusCode","code","__publicField","AuthError","ForbiddenError","NotFoundError","PurchaseError","DEFAULT_BASE_URL","HttpClient","config","path","params","body","url","method","isRetry","token","headers","response","REFRESH_THRESHOLD_MS","TokenManager","options","stored","refreshed","tokens","refreshToken","_err","MemoryTokenStorage","parseExpiresAt","expiresAt","BrowserAuthNamespace","http","tokenManager","res","BrowserConfigNamespace","BrowserContentNamespace","id","userId","BrowserPurchasesNamespace","BrowserWalletNamespace","sessionId","CheckoutNamespace","contentId","init","baseUrl","storage","data","BrowserClient","_http","_tokenManager","_config","DEFAULT_STORAGE_KEY","localStorageAdapter","key","raw"],"mappings":";;;AAgBO,MAAMA,UAAsB,MAAM;AAAA,EAOvC,YAAYC,GAAiBC,GAAoBC,GAAe;AAC9D,UAAMF,CAAO;AANC;AAAA,IAAAG,EAAA;AAGA;AAAA,IAAAA,EAAA;AAId,SAAK,OAAO,iBACZ,KAAK,aAAaF,GAClB,KAAK,OAAOC,GAEZ,OAAO,eAAe,MAAM,WAAW,SAAS;AAAA,EAClD;AACF;AAMO,MAAME,UAAkBL,EAAc;AAAA,EAC3C,YAAYC,GAAiBE,GAAe;AAC1C,UAAMF,GAAS,KAAKE,CAAI,GACxB,KAAK,OAAO;AAAA,EACd;AACF;AAMO,MAAMG,UAAuBN,EAAc;AAAA,EAChD,YAAYC,GAAiBE,GAAe;AAC1C,UAAMF,GAAS,KAAKE,CAAI,GACxB,KAAK,OAAO;AAAA,EACd;AACF;AAKO,MAAMI,UAAsBP,EAAc;AAAA,EAC/C,YAAYC,GAAiBE,GAAe;AAC1C,UAAMF,GAAS,KAAKE,CAAI,GACxB,KAAK,OAAO;AAAA,EACd;AACF;AAMO,MAAMK,UAAsBR,EAAc;AAAA,EAC/C,YAAYC,GAAiBC,GAAoBC,GAAe;AAC9D,UAAMF,GAASC,GAAYC,CAAI,GAC/B,KAAK,OAAO;AAAA,EACd;AACF;ACjDA,MAAMM,IAAmB;AAalB,MAAMC,EAAW;AAAA,EAKtB,YAAYC,IAA2B,IAAI;AAJ1B,IAAAP,EAAA;AACA,IAAAA,EAAA;AACA,IAAAA,EAAA;AAGf,SAAK,WAAWO,EAAO,WAAWF,GAAkB,QAAQ,OAAO,EAAE,GACrE,KAAK,iBAAiBE,EAAO,mBAAmB,MAAM,OACtD,KAAK,iBAAiBA,EAAO,mBAAmB,MAAM;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,IAAOC,GAAcC,GAA6C;AACtE,WAAO,KAAK,QAAW,OAAO,KAAK,SAASD,GAAMC,CAAM,CAAC;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,KAAQD,GAAcE,GAA4B;AACtD,WAAO,KAAK,QAAW,QAAQ,KAAK,SAASF,CAAI,GAAGE,CAAI;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,IAAOF,GAAcE,GAA4B;AACrD,WAAO,KAAK,QAAW,OAAO,KAAK,SAASF,CAAI,GAAGE,CAAI;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,MAASF,GAAcE,GAA4B;AACvD,WAAO,KAAK,QAAW,SAAS,KAAK,SAASF,CAAI,GAAGE,CAAI;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,OAAiBF,GAA0B;AAC/C,WAAO,KAAK,QAAW,UAAU,KAAK,SAASA,CAAI,CAAC;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA,EAMQ,SAASA,GAAcC,GAAyC;AACtE,UAAME,IAAM,GAAG,KAAK,OAAO,GAAGH,CAAI;AAClC,WAAI,CAACC,KAAU,OAAO,KAAKA,CAAM,EAAE,WAAW,IAAUE,IACjD,GAAGA,CAAG,IAAI,IAAI,gBAAgBF,CAAM,EAAE,UAAU;AAAA,EACzD;AAAA,EAEA,MAAc,QACZG,GACAD,GACAD,GACAG,IAAU,IACE;AACZ,UAAMC,IAAQ,MAAM,KAAK,eAAA,GACnBC,IAAkC;AAAA,MACtC,gBAAgB;AAAA,MAChB,QAAQ;AAAA,IAAA;AAEV,IAAID,MAAOC,EAAQ,gBAAmB,UAAUD,CAAK;AAErD,UAAME,IAAW,MAAM,MAAML,GAAK;AAAA,MAChC,QAAAC;AAAA,MACA,SAAAG;AAAA,MACA,GAAIL,MAAS,UAAa,EAAE,MAAM,KAAK,UAAUA,CAAI,EAAA;AAAA,IAAE,CACxD;AAED,QAAIM,EAAS,WAAW,OAAO,CAACH,GAAS;AAEvC,UADiB,MAAM,KAAK,eAAA;AAE1B,eAAO,KAAK,QAAWD,GAAQD,GAAKD,GAAM,EAAI;AAEhD,YAAM,IAAIT,EAAU,0CAA0C;AAAA,IAChE;AAMA,QAJKe,EAAS,MACZ,MAAM,KAAK,cAAcA,CAAQ,GAG/BA,EAAS,WAAW;AAIxB,aAAOA,EAAS,KAAA;AAAA,EAClB;AAAA,EAEA,MAAc,cAAcA,GAAoC;AAC9D,QAAIN;AACJ,QAAI;AACF,MAAAA,IAAQ,MAAMM,EAAS,KAAA;AAAA,IACzB,QAAQ;AAAA,IAER;AAEA,UAAMnB,IAAUa,GAAM,MAAM,WAAWM,EAAS,YAC1CjB,IAAOW,GAAM,MAAM;AAEzB,YAAQM,EAAS,QAAA;AAAA,MACf,KAAK;AACH,cAAM,IAAIf,EAAUJ,GAASE,CAAI;AAAA,MACnC,KAAK;AACH,cAAM,IAAIG,EAAeL,GAASE,CAAI;AAAA,MACxC,KAAK;AACH,cAAM,IAAII,EAAcN,GAASE,CAAI;AAAA,MACvC;AACE,cAAM,IAAIH,EAAcC,GAASmB,EAAS,QAAQjB,CAAI;AAAA,IAAA;AAAA,EAE5D;AACF;AC/JA,MAAMkB,IAAuB;AA+CtB,MAAMC,EAAa;AAAA,EAOxB,YAAYC,GAA8B;AANzB,IAAAnB,EAAA;AACA,IAAAA,EAAA;AACA,IAAAA,EAAA;AACA,IAAAA,EAAA;AACT,IAAAA,EAAA,wBAAsD;AAG5D,SAAK,UAAUmB,EAAQ,SACvB,KAAK,YAAYA,EAAQ,WACzB,KAAK,mBAAmBA,EAAQ,kBAChC,KAAK,gBAAgBA,EAAQ;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,iBAAyC;AAC7C,UAAMC,IAAS,MAAM,KAAK,QAAQ,UAAA;AAClC,WAAKA,IAEeA,EAAO,YAAY,KAAK,IAAA,IAC1BH,KACE,MAAM,KAAK,eAAeG,EAAO,YAAY,IAC7C,eAAe,OAG5BA,EAAO,cARM;AAAA,EAStB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,qBAA6C;AACjD,UAAMA,IAAS,MAAM,KAAK,QAAQ,UAAA;AAClC,QAAI,CAACA;AACH,mBAAM,KAAK,gBAAA,GACJ;AAET,UAAMC,IAAY,MAAM,KAAK,eAAeD,EAAO,YAAY;AAC/D,WAAKC,KAAW,MAAM,KAAK,gBAAA,GACpBA,GAAW,eAAe;AAAA,EACnC;AAAA;AAAA,EAGA,MAAM,UAAUC,GAAqC;AACnD,UAAM,KAAK,QAAQ,UAAUA,CAAM;AAAA,EACrC;AAAA;AAAA,EAGA,MAAM,cAA6B;AACjC,UAAM,KAAK,QAAQ,YAAA;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAMQ,eAAeC,GAAoD;AACzE,WAAI,KAAK,iBAAuB,KAAK,kBAErC,KAAK,iBAAiB,KAAK,UAAUA,CAAY,EAC9C,KAAK,OAAOD,OACX,MAAM,KAAK,QAAQ,UAAUA,CAAM,GACnC,MAAM,KAAK,mBAAmBA,CAAM,GAC7BA,EACR,EACA,MAAM,CAACE,MAAkB;AACxB,YAAK,KAAK,QAAQ,YAAA,GACZ,IAAIvB,EAAU,+CAA+C;AAAA,IACrE,CAAC,EACA,QAAQ,MAAM;AACb,WAAK,iBAAiB;AAAA,IACxB,CAAC,GAEI,KAAK;AAAA,EACd;AACF;AAWO,MAAMwB,EAA2C;AAAA,EAAjD;AACG,IAAAzB,EAAA,gBAA8B;AAAA;AAAA,EAEtC,YAAiC;AAC/B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,UAAUsB,GAA4B;AACpC,SAAK,SAASA;AAAA,EAChB;AAAA,EAEA,cAAoB;AAClB,SAAK,SAAS;AAAA,EAChB;AACF;AAaO,SAASI,EAAeC,GAA2B;AACxD,SAAO,IAAI,KAAKA,CAAS,EAAE,QAAA;AAC7B;AC9IO,MAAMC,EAAqB;AAAA;AAAA,EAEhC,YACmBC,GACAC,GACjB;AAFiB,SAAA,OAAAD,GACA,KAAA,eAAAC;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASH,MAAM,OAAOpB,GAA0D;AACrE,UAAMqB,IAAM,MAAM,KAAK,KAAK,KAA6B,mBAAmBrB,CAAI;AAChF,iBAAM,KAAK,YAAYqB,CAAG,GACnBA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,eAAerB,GAA8D;AACjF,UAAMqB,IAAM,MAAM,KAAK,KAAK,KAA6B,wBAAwBrB,CAAI;AACrF,iBAAM,KAAK,YAAYqB,CAAG,GACnBA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,gBAAgBrB,GAA8D;AAClF,UAAMqB,IAAM,MAAM,KAAK,KAAK,KAA6B,yBAAyBrB,CAAI;AACtF,iBAAM,KAAK,YAAYqB,CAAG,GACnBA;AAAA,EACT;AAAA,EAEA,MAAc,YAAYA,GAA4C;AACpE,UAAM,KAAK,aAAa,UAAU;AAAA,MAChC,aAAaA,EAAI;AAAA,MACjB,cAAcA,EAAI;AAAA,MAClB,WAAWL,EAAeK,EAAI,UAAU;AAAA,IAAA,CACzC;AAAA,EACH;AACF;AClEO,MAAMC,EAAuB;AAAA;AAAA,EAElC,YAA+BH,GAAkB;AAAlB,SAAA,OAAAA;AAAA,EAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBlD,MAAM,YAA2C;AAC/C,WAAO,KAAK,KAAK,IAA0B,mBAAmB;AAAA,EAChE;AACF;ACtBO,MAAMI,EAAwB;AAAA,EACnC,YAA+BJ,GAAkB;AAAlB,SAAA,OAAAA;AAAA,EAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUlD,MAAM,cAAcK,GAAYC,GAAqD;AACnF,UAAM1B,IAAiC,CAAA;AACvC,WAAI0B,MAAW,WACb1B,EAAO,UAAa0B,IAEf,KAAK,KAAK;AAAA,MACf,eAAeD,CAAE;AAAA,MACjB,OAAO,KAAKzB,CAAM,EAAE,SAAS,IAAIA,IAAS;AAAA,IAAA;AAAA,EAE9C;AACF;ACrBO,MAAM2B,EAA0B;AAAA,EACrC,YAA+BP,GAAkB;AAAlB,SAAA,OAAAA;AAAA,EAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQlD,MAAM,OAAOnB,GAAwD;AACnE,WAAO,KAAK,KAAK,KAAuB,iBAAiBA,CAAI;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,OAAoC;AACxC,WAAO,KAAK,KAAK,IAAwB,eAAe;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,IAAIwB,GAAuC;AAC/C,WAAO,KAAK,KAAK,IAAsB,iBAAiBA,CAAE,EAAE;AAAA,EAC9D;AACF;ACzBO,MAAMG,EAAuB;AAAA,EAClC,YAA+BR,GAAkB;AAAlB,SAAA,OAAAA;AAAA,EAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOlD,MAAM,UAA0C;AAC9C,WAAO,KAAK,KAAK,IAA2B,oBAAoB;AAAA,EAClE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,eAAiD;AACrD,WAAO,KAAK,KAAK,IAA6B,yBAAyB;AAAA,EACzE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,qBACJnB,GACuC;AACvC,WAAO,KAAK,KAAK,KAAmC,8BAA8BA,CAAI;AAAA,EACxF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,iBAAiB4B,GAAyD;AAC9E,WAAO,KAAK,KAAK,IAAiC,6BAA6BA,CAAS,EAAE;AAAA,EAC5F;AACF;AC/CO,MAAMC,EAAkB;AAAA,EAC7B,YAA+BV,GAAkB;AAAlB,SAAA,OAAAA;AAAA,EAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAalD,MAAM,MAAMW,GAAmD;AAC7D,WAAO,KAAK,KAAK,IAA2B,sBAAsBA,CAAS,EAAE;AAAA,EAC/E;AACF;ACkCO,SAASC,EAAKlC,GAA4C;AAC/D,QAAMmC,IAAUnC,EAAO,WAAW,4BAC5BoC,IAAUpC,EAAO,WAAW,IAAIkB,EAAA,GAEhCK,IAAe,IAAIZ,EAAa;AAAA,IACpC,SAAAyB;AAAA,IACA,WAAW,OAAOpB,MAAiB;AACjC,YAAMQ,IAAM,MAAM,MAAM,GAAGW,CAAO,0BAA0B;AAAA,QAC1D,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAA;AAAA,QAC3B,MAAM,KAAK,UAAU,EAAE,eAAenB,GAAc;AAAA,MAAA,CACrD;AACD,UAAI,CAACQ,EAAI,GAAI,OAAM,IAAI,MAAM,sBAAsB;AACnD,YAAMa,IAAQ,MAAMb,EAAI,KAAA;AAKxB,aAAO;AAAA,QACL,aAAaa,EAAK;AAAA,QAClB,cAAcA,EAAK;AAAA,QACnB,WAAWlB,EAAekB,EAAK,UAAU;AAAA,MAAA;AAAA,IAE7C;AAAA,IACA,GAAIrC,EAAO,kBAAkB,UAAa,EAAE,eAAeA,EAAO,cAAA;AAAA,EAAc,CACjF,GAEKsB,IAAO,IAAIvB,EAAW;AAAA,IAC1B,SAAAoC;AAAA,IACA,gBAAgB,MAAMZ,EAAa,eAAA;AAAA,IACnC,gBAAgB,MAAMA,EAAa,mBAAA;AAAA,EAAmB,CACvD;AAED,SAAO,IAAIe,EAAchB,GAAMC,GAAcvB,CAAM;AACrD;AASO,MAAMsC,EAAc;AAAA;AAAA,EAoBzB,YACkBC,GACAC,GACAC,GAChB;AAtBO;AAAA,IAAAhD,EAAA;AAGA;AAAA,IAAAA,EAAA;AAGA;AAAA,IAAAA,EAAA;AAGA;AAAA,IAAAA,EAAA;AAGA;AAAA,IAAAA,EAAA;AAGA;AAAA,IAAAA,EAAA;AAIS,SAAA,QAAA8C,GACA,KAAA,gBAAAC,GACA,KAAA,UAAAC,GAEhB,KAAK,SAAS,IAAIhB,EAAuBc,CAAK,GAC9C,KAAK,OAAO,IAAIlB,EAAqBkB,GAAOC,CAAa,GACzD,KAAK,WAAW,IAAIR,EAAkBO,CAAK,GAC3C,KAAK,SAAS,IAAIT,EAAuBS,CAAK,GAC9C,KAAK,YAAY,IAAIV,EAA0BU,CAAK,GACpD,KAAK,UAAU,IAAIb,EAAwBa,CAAK;AAAA,EAClD;AACF;AC3IA,MAAMG,IAAsB;AA0BrB,SAASC,EAAoBC,IAAMF,GAAmC;AAC3E,SAAO;AAAA,IACL,YAAiC;AAC/B,YAAMG,IAAM,aAAa,QAAQD,CAAG;AACpC,UAAI,CAACC,EAAK,QAAO;AACjB,UAAI;AACF,eAAO,KAAK,MAAMA,CAAG;AAAA,MACvB,QAAQ;AAEN,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,UAAU9B,GAA4B;AACpC,mBAAa,QAAQ6B,GAAK,KAAK,UAAU7B,CAAM,CAAC;AAAA,IAClD;AAAA,IACA,cAAoB;AAClB,mBAAa,WAAW6B,CAAG;AAAA,IAC7B;AAAA,EAAA;AAEJ;"}
1
+ {"version":3,"file":"index.js","sources":["../../core/src/errors.ts","../../core/src/http-client.ts","../../core/src/token-manager.ts","../src/resources/auth.ts","../src/resources/config.ts","../src/resources/content.ts","../src/resources/purchases.ts","../src/resources/seller/content.ts","../src/resources/seller/index.ts","../src/resources/wallet.ts","../src/resources/checkout.ts","../src/client.ts","../src/local-storage-adapter.ts"],"sourcesContent":["/**\n * Base error class for all LedeWire SDK errors.\n * All errors thrown by the SDK are instances of this class,\n * making it safe to use `err instanceof LedewireError` as a type guard.\n *\n * @example\n * ```ts\n * try {\n * await client.purchases.create({ contentId: '...' })\n * } catch (err) {\n * if (err instanceof LedewireError) {\n * console.error(err.statusCode, err.message)\n * }\n * }\n * ```\n */\nexport class LedewireError extends Error {\n /** HTTP status code returned by the API (e.g. 400, 401, 404, 422). */\n public readonly statusCode: number\n\n /** Machine-readable error code from the API error body, if present. */\n public readonly code: number | undefined\n\n constructor(message: string, statusCode: number, code?: number) {\n super(message)\n this.name = 'LedewireError'\n this.statusCode = statusCode\n this.code = code\n // Restore prototype chain (required for extending built-in Error in TS)\n Object.setPrototypeOf(this, new.target.prototype)\n }\n}\n\n/**\n * Thrown when the request lacks valid authentication credentials,\n * or when a token refresh fails and re-authentication is required.\n */\nexport class AuthError extends LedewireError {\n constructor(message: string, code?: number) {\n super(message, 401, code)\n this.name = 'AuthError'\n }\n}\n\n/**\n * Thrown when the authenticated user does not have permission\n * to perform the requested operation.\n *\n * **Common cause — merchant login with the wrong account role:**\n * `POST /v1/auth/merchant/login/email` and `POST /v1/auth/merchant/login/google`\n * return `403 Forbidden` (not `401`) when the credentials are valid but the\n * account has no merchant store associations (e.g. a buyer account used on the\n * merchant endpoint). The `err.message` will be:\n * `\"This account does not have merchant access. Use a merchant or owner account.\"`\n *\n * Use a different account or ask a store owner to add your account as a member.\n *\n * @example\n * ```ts\n * import { ForbiddenError, AuthError } from '@ledewire/node'\n *\n * try {\n * await client.merchant.auth.loginWithGoogle({ id_token })\n * } catch (err) {\n * if (err instanceof ForbiddenError) {\n * // Credentials were valid but account has no merchant store access.\n * // err.message → \"This account does not have merchant access. Use a merchant or owner account.\"\n * console.error('Wrong account role:', err.message)\n * } else if (err instanceof AuthError) {\n * // Bad credentials or expired token — re-authenticate.\n * console.error('Authentication failed:', err.message)\n * }\n * }\n * ```\n */\nexport class ForbiddenError extends LedewireError {\n constructor(message: string, code?: number) {\n super(message, 403, code)\n this.name = 'ForbiddenError'\n }\n}\n\n/**\n * Thrown when the requested resource does not exist.\n */\nexport class NotFoundError extends LedewireError {\n constructor(message: string, code?: number) {\n super(message, 404, code)\n this.name = 'NotFoundError'\n }\n}\n\n/**\n * Thrown when the purchase cannot be completed due to a validation\n * failure, such as a price mismatch or a duplicate purchase.\n */\nexport class PurchaseError extends LedewireError {\n constructor(message: string, statusCode: number, code?: number) {\n super(message, statusCode, code)\n this.name = 'PurchaseError'\n }\n}\n","import type { components } from './api.gen.js'\nimport { AuthError, ForbiddenError, LedewireError, NotFoundError } from './errors.js'\n\n/** @internal */\ntype ApiErrorBody = components['schemas']['ErrorResponse']\n\n/**\n * Configuration options for `HttpClient`.\n */\nexport interface HttpClientConfig {\n /** API base URL. Defaults to the production API. */\n baseUrl?: string\n /**\n * Returns the current access token (or null) before each request.\n * If null, requests are sent without an Authorization header.\n */\n getAccessToken?: () => string | null | Promise<string | null>\n /**\n * Called when a 401 is received on the first attempt.\n * Should refresh and return the new access token, or null to propagate\n * an `AuthError` to the caller.\n */\n onUnauthorized?: () => string | null | Promise<string | null>\n}\n\nconst DEFAULT_BASE_URL = 'https://api.ledewire.com'\n\n/**\n * Core HTTP client used by all SDK packages.\n *\n * Features:\n * - Injects `Authorization: Bearer <token>` headers automatically\n * - Maps HTTP error responses to typed `LedewireError` subclasses\n * - On receiving a 401, calls `onUnauthorized` once and retries the request\n *\n * This is an internal class - consumers should use the\n * package-level client factories (`init` / `createClient`) instead.\n */\nexport class HttpClient {\n private readonly baseUrl: string\n private readonly getAccessToken: NonNullable<HttpClientConfig['getAccessToken']>\n private readonly onUnauthorized: NonNullable<HttpClientConfig['onUnauthorized']>\n\n constructor(config: HttpClientConfig = {}) {\n this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, '')\n this.getAccessToken = config.getAccessToken ?? (() => null)\n this.onUnauthorized = config.onUnauthorized ?? (() => null)\n }\n\n /**\n * GET request with optional query parameters.\n * @param path - API path (e.g. `/v1/wallet/balance`)\n * @param params - Query string parameters\n */\n async get<T>(path: string, params?: Record<string, string>): Promise<T> {\n return this.request<T>('GET', this.buildUrl(path, params))\n }\n\n /**\n * POST request.\n * @param path - API path\n * @param body - Request body (JSON-serialized)\n */\n async post<T>(path: string, body?: unknown): Promise<T> {\n return this.request<T>('POST', this.buildUrl(path), body)\n }\n\n /**\n * PUT request.\n * @param path - API path\n * @param body - Request body (JSON-serialized)\n */\n async put<T>(path: string, body?: unknown): Promise<T> {\n return this.request<T>('PUT', this.buildUrl(path), body)\n }\n\n /**\n * PATCH request.\n * @param path - API path\n * @param body - Partial update body (JSON-serialized)\n */\n async patch<T>(path: string, body?: unknown): Promise<T> {\n return this.request<T>('PATCH', this.buildUrl(path), body)\n }\n\n /**\n * DELETE request.\n * @param path - API path\n */\n async delete<T = void>(path: string): Promise<T> {\n return this.request<T>('DELETE', this.buildUrl(path))\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private buildUrl(path: string, params?: Record<string, string>): string {\n const url = `${this.baseUrl}${path}`\n if (!params || Object.keys(params).length === 0) return url\n return `${url}?${new URLSearchParams(params).toString()}`\n }\n\n private async request<T>(\n method: string,\n url: string,\n body?: unknown,\n isRetry = false,\n ): Promise<T> {\n const token = await this.getAccessToken()\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n }\n if (token) headers['Authorization'] = `Bearer ${token}`\n\n const response = await fetch(url, {\n method,\n headers,\n ...(body !== undefined && { body: JSON.stringify(body) }),\n })\n\n if (response.status === 401 && !isRetry) {\n const newToken = await this.onUnauthorized()\n if (newToken) {\n return this.request<T>(method, url, body, true)\n }\n throw new AuthError('Session expired. Please re-authenticate.')\n }\n\n if (!response.ok) {\n await this.throwApiError(response)\n }\n\n if (response.status === 204) {\n return undefined as T\n }\n\n return response.json() as Promise<T>\n }\n\n private async throwApiError(response: Response): Promise<never> {\n let body: ApiErrorBody | undefined\n try {\n body = (await response.json()) as ApiErrorBody\n } catch {\n // Non-JSON body - fall through to use statusText\n }\n\n const message = body?.error.message ?? response.statusText\n const code = body?.error.code\n\n switch (response.status) {\n case 401:\n throw new AuthError(message, code)\n case 403:\n throw new ForbiddenError(message, code)\n case 404:\n throw new NotFoundError(message, code)\n default:\n throw new LedewireError(message, response.status, code)\n }\n }\n}\n","import type { StoredTokens, TokenStorage } from './types.js'\nimport { AuthError } from './errors.js'\n\n/** How many milliseconds before expiry to proactively refresh the token. */\nconst REFRESH_THRESHOLD_MS = 60_000 // 60 seconds\n\n/**\n * Options for constructing a `TokenManager`.\n */\nexport interface TokenManagerOptions {\n storage: TokenStorage\n /**\n * Exchanges a refresh token for a new token pair.\n * Implemented by each package's client factory using the\n * `/v1/auth/token/refresh` endpoint.\n */\n refreshFn: (refreshToken: string) => Promise<StoredTokens>\n /**\n * Called after a successful background token refresh (not on initial login).\n * Use for side-effects only — e.g. audit logging, cache invalidation, or\n * notifying a secondary system when tokens rotate.\n *\n * **Do not use this for token persistence.** The `storage` adapter's\n * `setTokens` is the canonical persistence hook and is already called on\n * every refresh. Providing both will result in double-writes.\n *\n * @example\n * ```ts\n * onTokenRefreshed: async (tokens) => {\n * await auditLog.record('token_refreshed', { expiresAt: tokens.expiresAt })\n * }\n * ```\n */\n onTokenRefreshed?: (tokens: StoredTokens) => void | Promise<void>\n /**\n * Called when a refresh attempt fails and the user must re-authenticate.\n * Typically used to redirect to login or emit a UI event.\n *\n * @example\n * ```ts\n * onAuthExpired: () => { window.location.href = '/login' }\n * ```\n */\n onAuthExpired?: () => void | Promise<void>\n}\n\n/**\n * Manages the JWT token lifecycle transparently.\n *\n * - Proactively refreshes the access token when it expires within 60 seconds\n * - Handles reactive refresh on 401 responses from the HTTP client\n * - Deduplicates concurrent refresh calls (only one in-flight refresh at a time)\n * - Fires `onAuthExpired` when refresh fails so the UI can prompt re-login\n *\n * This is an internal class - use the package-level client factories instead.\n */\nexport class TokenManager {\n private readonly storage: TokenStorage\n private readonly refreshFn: TokenManagerOptions['refreshFn']\n private readonly onTokenRefreshed: TokenManagerOptions['onTokenRefreshed']\n private readonly onAuthExpired: TokenManagerOptions['onAuthExpired']\n private refreshPromise: Promise<StoredTokens | null> | null = null\n\n constructor(options: TokenManagerOptions) {\n this.storage = options.storage\n this.refreshFn = options.refreshFn\n this.onTokenRefreshed = options.onTokenRefreshed\n this.onAuthExpired = options.onAuthExpired\n }\n\n /**\n * Returns a valid access token, refreshing proactively if needed.\n * Returns `null` if no tokens are stored (user not authenticated).\n */\n async getAccessToken(): Promise<string | null> {\n const stored = await this.storage.getTokens()\n if (!stored) return null\n\n const expiresInMs = stored.expiresAt - Date.now()\n if (expiresInMs < REFRESH_THRESHOLD_MS) {\n const refreshed = await this.performRefresh(stored.refreshToken)\n return refreshed?.accessToken ?? null\n }\n\n return stored.accessToken\n }\n\n /**\n * Called by `HttpClient` when a 401 is received.\n * Attempts one refresh; returns the new access token or null.\n */\n async handleUnauthorized(): Promise<string | null> {\n const stored = await this.storage.getTokens()\n if (!stored) {\n await this.onAuthExpired?.()\n return null\n }\n const refreshed = await this.performRefresh(stored.refreshToken)\n if (!refreshed) await this.onAuthExpired?.()\n return refreshed?.accessToken ?? null\n }\n\n /** Store new tokens after a successful login or signup. */\n async setTokens(tokens: StoredTokens): Promise<void> {\n await this.storage.setTokens(tokens)\n }\n\n /** Clear all stored tokens (call on logout). */\n async clearTokens(): Promise<void> {\n await this.storage.clearTokens()\n }\n\n // ---------------------------------------------------------------------------\n // Private\n // ---------------------------------------------------------------------------\n\n private performRefresh(refreshToken: string): Promise<StoredTokens | null> {\n if (this.refreshPromise) return this.refreshPromise\n\n this.refreshPromise = this.refreshFn(refreshToken)\n .then(async (tokens) => {\n await this.storage.setTokens(tokens)\n await this.onTokenRefreshed?.(tokens)\n return tokens\n })\n .catch((_err: unknown) => {\n void this.storage.clearTokens()\n throw new AuthError('Token refresh failed. Please re-authenticate.')\n })\n .finally(() => {\n this.refreshPromise = null\n })\n\n return this.refreshPromise\n }\n}\n\n// ---------------------------------------------------------------------------\n// Built-in storage adapters\n// ---------------------------------------------------------------------------\n\n/**\n * In-memory token storage (default for both packages).\n * Tokens are cleared when the page unloads or the process exits.\n * This is the most secure default for browser environments.\n */\nexport class MemoryTokenStorage implements TokenStorage {\n private tokens: StoredTokens | null = null\n\n getTokens(): StoredTokens | null {\n return this.tokens\n }\n\n setTokens(tokens: StoredTokens): void {\n this.tokens = tokens\n }\n\n clearTokens(): void {\n this.tokens = null\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Converts an `expires_at` ISO 8601 string from an API auth response\n * into a Unix timestamp (milliseconds) for use in `StoredTokens.expiresAt`.\n *\n * @param expiresAt - ISO 8601 datetime string, e.g. `\"2026-01-01T12:00:00Z\"`\n * @returns Unix timestamp in milliseconds\n */\nexport function parseExpiresAt(expiresAt: string): number {\n return new Date(expiresAt).getTime()\n}\n","/**\n * Buyer authentication namespace for browser environments.\n *\n * Tokens are stored automatically in the `TokenManager` after every\n * successful call — callers never need to manage tokens directly.\n *\n * @module\n */\nimport { parseExpiresAt } from '@ledewire/core'\nimport type { HttpClient, TokenManager } from '@ledewire/core'\nimport type {\n AuthLoginApiKeyRequest,\n AuthLoginEmailRequest,\n AuthLoginOAuthRequest,\n AuthSignupRequest,\n AuthenticationResponse,\n} from '@ledewire/core'\n\n/**\n * Buyer authentication: signup, email/password login, Google OAuth.\n *\n * Obtain via `lw.auth` — do not construct directly.\n *\n * @example\n * ```ts\n * const lw = Ledewire.init({ apiKey: 'your_api_key' })\n * await lw.auth.loginWithEmail({ email: 'u@example.com', password: 'pw' })\n * ```\n */\nexport class BrowserAuthNamespace {\n /** @internal */\n constructor(\n private readonly http: HttpClient,\n private readonly tokenManager: TokenManager,\n ) {}\n\n /**\n * Register a new buyer account with email and password.\n * Tokens are stored automatically after successful signup.\n *\n * @param body - Signup credentials and display name.\n * @returns The authentication token response.\n */\n async signup(body: AuthSignupRequest): Promise<AuthenticationResponse> {\n const res = await this.http.post<AuthenticationResponse>('/v1/auth/signup', body)\n await this.storeTokens(res)\n return res\n }\n\n /**\n * Log in with email and password.\n * Tokens are stored automatically after successful login.\n *\n * @param body - Email and password credentials.\n * @returns The authentication token response.\n */\n async loginWithEmail(body: AuthLoginEmailRequest): Promise<AuthenticationResponse> {\n const res = await this.http.post<AuthenticationResponse>('/v1/auth/login/email', body)\n await this.storeTokens(res)\n return res\n }\n\n /**\n * Log in with a Google ID token obtained from the Google OAuth flow.\n * Tokens are stored automatically after successful login.\n *\n * @param body - The Google ID token.\n * @returns The authentication token response.\n */\n async loginWithGoogle(body: AuthLoginOAuthRequest): Promise<AuthenticationResponse> {\n const res = await this.http.post<AuthenticationResponse>('/v1/auth/login/google', body)\n await this.storeTokens(res)\n return res\n }\n\n /**\n * Log in using an API key to obtain a seller token.\n * Provide only `key` for read-only (`view`) access.\n * Provide both `key` and `secret` for read/write (`full`) access.\n * Tokens are stored automatically after successful authentication.\n *\n * Use this before calling `lw.seller.content.*` methods.\n *\n * @param body - API key credentials.\n * @returns The authentication token response.\n *\n * @example\n * ```ts\n * // View access (read-only) — sufficient for seller.content.list/search/get\n * await lw.auth.loginWithApiKey({ key: 'your_api_key' })\n *\n * // Full access (read/write)\n * await lw.auth.loginWithApiKey({ key: 'your_api_key', secret: 'your_secret' })\n * ```\n */\n async loginWithApiKey(body: AuthLoginApiKeyRequest): Promise<AuthenticationResponse> {\n const res = await this.http.post<AuthenticationResponse>('/v1/auth/login/api-key', body)\n await this.storeTokens(res)\n return res\n }\n\n private async storeTokens(res: AuthenticationResponse): Promise<void> {\n await this.tokenManager.setTokens({\n accessToken: res.access_token,\n refreshToken: res.refresh_token,\n expiresAt: parseExpiresAt(res.expires_at),\n })\n }\n}\n","/**\n * Platform configuration namespace (browser).\n *\n * Provides access to public (unauthenticated) platform-level configuration.\n *\n * @module\n */\nimport type { HttpClient } from '@ledewire/core'\nimport type { PublicConfigResponse } from '@ledewire/core'\n\n/**\n * Platform-level configuration for browser clients.\n *\n * Obtain via `client.config` — do not construct directly.\n */\nexport class BrowserConfigNamespace {\n /** @internal */\n constructor(protected readonly http: HttpClient) {}\n\n /**\n * Returns platform-level public configuration.\n * No authentication required — safe to call before the user has signed in.\n *\n * Use `google_client_id` to initialise the Google Identity Services library\n * on the storefront login page without requiring a prior authenticated call.\n *\n * @example\n * ```ts\n * const lw = init({ apiKey: 'your_api_key' })\n * const { google_client_id } = await lw.config.getPublic()\n * google.accounts.id.initialize({ client_id: google_client_id, callback })\n * ```\n */\n async getPublic(): Promise<PublicConfigResponse> {\n return this.http.get<PublicConfigResponse>('/v1/config/public')\n }\n}\n","import type { HttpClient } from '@ledewire/core'\nimport type { ContentWithAccessResponse } from '@ledewire/core'\n\n/**\n * Buyer content namespace — fetch content with real-time access information.\n *\n * Obtain via `lw.content` — do not construct directly.\n *\n * @example\n * ```ts\n * const { content, access } = await lw.content.getWithAccess('content-id')\n * if (access.has_access) showFullContent(content)\n * ```\n */\nexport class BrowserContentNamespace {\n constructor(protected readonly http: HttpClient) {}\n\n /**\n * Returns a content item together with access information for the\n * authenticated buyer (or a generic access response when unauthenticated).\n *\n * @param id - The content ID.\n * @param userId - Optional user ID to check a specific user's access status.\n * @returns The content item with access information.\n */\n async getWithAccess(id: string, userId?: string): Promise<ContentWithAccessResponse> {\n const params: Record<string, string> = {}\n if (userId !== undefined) {\n params['user_id'] = userId\n }\n return this.http.get<ContentWithAccessResponse>(\n `/v1/content/${id}/with-access`,\n Object.keys(params).length > 0 ? params : undefined,\n )\n }\n}\n","import type { HttpClient } from '@ledewire/core'\nimport type { PurchaseCreateRequest, PurchaseResponse } from '@ledewire/core'\n\n/**\n * Buyer purchases namespace — create and retrieve content purchases.\n *\n * Obtain via `lw.purchases` — do not construct directly.\n *\n * @example\n * ```ts\n * await lw.purchases.create({ content_id: 'content-id' })\n * const all = await lw.purchases.list()\n * ```\n */\nexport class BrowserPurchasesNamespace {\n constructor(protected readonly http: HttpClient) {}\n\n /**\n * Completes a content purchase using the buyer's wallet balance.\n *\n * @param body - The content ID and expected price in cents.\n * @returns The completed purchase record.\n */\n async create(body: PurchaseCreateRequest): Promise<PurchaseResponse> {\n return this.http.post<PurchaseResponse>('/v1/purchases', body)\n }\n\n /**\n * Returns all purchases made by the authenticated buyer.\n *\n * @returns A list of purchase records, newest first.\n */\n async list(): Promise<PurchaseResponse[]> {\n return this.http.get<PurchaseResponse[]>('/v1/purchases')\n }\n\n /**\n * Returns a single purchase by ID.\n *\n * @param id - The purchase ID.\n * @returns The purchase record.\n */\n async get(id: string): Promise<PurchaseResponse> {\n return this.http.get<PurchaseResponse>(`/v1/purchases/${id}`)\n }\n}\n","/**\n * Seller content namespace for browser environments.\n *\n * Read-only operations using a view-permission API key token.\n * Hits the `/v1/seller/content` family of endpoints — store identity is\n * derived server-side from the token, so no `store_id` is required.\n *\n * @module\n */\nimport type { HttpClient } from '@ledewire/core'\nimport type { ContentResponse } from '@ledewire/core'\n\n/**\n * Search criteria for seller content search.\n * At least one field must be supplied. All supplied fields must match (AND logic).\n */\nexport interface SellerContentSearchRequest {\n /** Case-insensitive partial match against the content title. */\n title?: string\n /** Case-insensitive partial match against the content URI (`external_ref` content only). */\n uri?: string\n /** Exact key/value pairs to AND-match against content metadata. */\n metadata?: Record<string, unknown>\n}\n\n/**\n * Seller content namespace — list, search, and fetch store content by API key.\n *\n * Obtain via `lw.seller.content` — do not construct directly.\n *\n * @example\n * ```ts\n * const lw = Ledewire.init({ apiKey: 'your_api_key' })\n * await lw.auth.loginWithApiKey({ key: 'your_api_key' })\n *\n * const items = await lw.seller.content.list()\n * const results = await lw.seller.content.search({ title: 'intro' })\n * const item = await lw.seller.content.get('content-id')\n * ```\n */\nexport class BrowserSellerContentNamespace {\n /** @internal */\n constructor(private readonly http: HttpClient) {}\n\n /**\n * List all content for the authenticated seller's store.\n * Requires a token with at least `view` permission.\n *\n * @returns Array of content items.\n *\n * @example\n * ```ts\n * const items = await lw.seller.content.list()\n * ```\n */\n async list(): Promise<ContentResponse[]> {\n return this.http.get<ContentResponse[]>('/v1/seller/content')\n }\n\n /**\n * Search content by title, URI, and/or metadata (AND logic).\n * At least one criterion must be supplied.\n * Requires a token with at least `view` permission.\n *\n * @param body - Search criteria (title, uri, and/or metadata).\n * @returns Array of matching content items.\n *\n * @example\n * ```ts\n * const results = await lw.seller.content.search({ title: 'intro' })\n * const byUri = await lw.seller.content.search({ uri: 'vimeo.com' })\n * const byMeta = await lw.seller.content.search({ metadata: { author: 'Alice' } })\n * ```\n */\n async search(body: SellerContentSearchRequest): Promise<ContentResponse[]> {\n return this.http.post<ContentResponse[]>('/v1/seller/content/search', body)\n }\n\n /**\n * Get a single content item by ID.\n * Requires a token with at least `view` permission.\n *\n * @param id - The content ID.\n * @returns The content item.\n *\n * @example\n * ```ts\n * const item = await lw.seller.content.get('content-id')\n * ```\n */\n async get(id: string): Promise<ContentResponse> {\n return this.http.get<ContentResponse>(`/v1/seller/content/${id}`)\n }\n}\n","/**\n * Browser seller namespace — API-key-authenticated read operations.\n *\n * @module\n */\nimport type { HttpClient } from '@ledewire/core'\nimport { BrowserSellerContentNamespace } from './content.js'\n\n/**\n * Seller operations accessible with a view-permission API key token.\n *\n * Obtain via `lw.seller` — do not construct directly.\n *\n * @example\n * ```ts\n * const lw = Ledewire.init({ apiKey: 'your_api_key' })\n * await lw.auth.loginWithApiKey({ key: 'your_api_key' })\n * const items = await lw.seller.content.list()\n * ```\n */\nexport class BrowserSellerNamespace {\n /** Seller content: list, search, and get by API key. */\n readonly content: BrowserSellerContentNamespace\n\n /** @internal */\n constructor(http: HttpClient) {\n this.content = new BrowserSellerContentNamespace(http)\n }\n}\n","import type { HttpClient } from '@ledewire/core'\nimport type {\n WalletBalanceResponse,\n WalletPaymentSessionRequest,\n WalletPaymentSessionResponse,\n WalletPaymentStatusResponse,\n WalletTransactionItem,\n} from '@ledewire/core'\n\n/**\n * Buyer wallet namespace — balance, transactions, and payment session management.\n *\n * Obtain via `lw.wallet` — do not construct directly.\n *\n * @example\n * ```ts\n * const { balance_cents } = await lw.wallet.balance()\n * const session = await lw.wallet.createPaymentSession({ amount_cents: 500 })\n * ```\n */\nexport class BrowserWalletNamespace {\n constructor(protected readonly http: HttpClient) {}\n\n /**\n * Returns the authenticated buyer's current wallet balance.\n *\n * @returns The current wallet balance in cents.\n */\n async balance(): Promise<WalletBalanceResponse> {\n return this.http.get<WalletBalanceResponse>('/v1/wallet/balance')\n }\n\n /**\n * Returns the authenticated buyer's wallet transaction history, newest first.\n *\n * @returns A list of completed wallet transaction entries.\n */\n async transactions(): Promise<WalletTransactionItem[]> {\n return this.http.get<WalletTransactionItem[]>('/v1/wallet/transactions')\n }\n\n /**\n * Creates a payment session for funding the buyer's wallet.\n *\n * @param body - The amount and currency to fund.\n * @returns Payment session details for use with the payment provider widget.\n */\n async createPaymentSession(\n body: WalletPaymentSessionRequest,\n ): Promise<WalletPaymentSessionResponse> {\n return this.http.post<WalletPaymentSessionResponse>('/v1/wallet/payment-session', body)\n }\n\n /**\n * Polls the status of a wallet funding payment session.\n *\n * @param sessionId - The session ID returned by `createPaymentSession`.\n * @returns The current payment status.\n */\n async getPaymentStatus(sessionId: string): Promise<WalletPaymentStatusResponse> {\n return this.http.get<WalletPaymentStatusResponse>(`/v1/wallet/payment-status/${sessionId}`)\n }\n}\n","import type { HttpClient } from '@ledewire/core'\nimport type { CheckoutStateResponse } from '@ledewire/core'\n\n/**\n * Checkout namespace — buyer checkout state for a specific content item.\n *\n * Obtain via `lw.checkout` — do not construct directly.\n *\n * @example\n * ```ts\n * const state = await lw.checkout.state('content-id')\n * // state.checkout_state.next_required_action:\n * // 'authenticate' | 'fund_wallet' | 'purchase' | 'view_content'\n * ```\n */\nexport class CheckoutNamespace {\n constructor(protected readonly http: HttpClient) {}\n\n /**\n * Returns the authenticated buyer's checkout readiness for a given piece of\n * content. Indicates whether the buyer needs to authenticate, fund their\n * wallet, or purchase before accessing the content.\n *\n * Works without authentication — returns anonymous checkout state when the\n * buyer is not logged in.\n *\n * @param contentId - The content item ID.\n * @returns The checkout state, including what action is required next.\n */\n async state(contentId: string): Promise<CheckoutStateResponse> {\n return this.http.get<CheckoutStateResponse>(`/v1/checkout/state/${contentId}`)\n }\n}\n","import { HttpClient, MemoryTokenStorage, TokenManager, parseExpiresAt } from '@ledewire/core'\nimport type { TokenStorage } from '@ledewire/core'\nimport { BrowserAuthNamespace } from './resources/auth.js'\nimport { BrowserConfigNamespace } from './resources/config.js'\nimport { BrowserContentNamespace } from './resources/content.js'\nimport { BrowserPurchasesNamespace } from './resources/purchases.js'\nimport { BrowserSellerNamespace } from './resources/seller/index.js'\nimport { BrowserWalletNamespace } from './resources/wallet.js'\nimport { CheckoutNamespace } from './resources/checkout.js'\n\n/**\n * Configuration options for the LedeWire browser client.\n */\nexport interface BrowserClientConfig {\n /**\n * LedeWire API key that identifies the store.\n * Obtained from the LedeWire merchant dashboard.\n */\n apiKey: string\n /**\n * Override the API base URL.\n * Defaults to `https://api.ledewire.com`.\n */\n baseUrl?: string\n /**\n * Token storage adapter.\n * Defaults to {@link MemoryTokenStorage} (in-memory, most secure).\n *\n * To persist sessions across page reloads, use the built-in\n * `localStorageAdapter`:\n * ```ts\n * import { init, localStorageAdapter } from '@ledewire/browser'\n * const lw = init({ apiKey: '...', storage: localStorageAdapter() })\n * ```\n */\n storage?: TokenStorage\n /**\n * Called when the user's session expires and cannot be refreshed.\n * Use this to show a re-authentication prompt.\n *\n * @example\n * ```ts\n * onAuthExpired: () => showLoginModal()\n * ```\n */\n onAuthExpired?: () => void\n}\n\n/**\n * Initialises a LedeWire browser client.\n * This is the primary entry point for the browser SDK.\n *\n * @example CDN usage:\n * ```html\n * <script src=\"https://cdn.jsdelivr.net/npm/@ledewire/browser@1/dist/ledewire.min.js\"></script>\n * <script>\n * const lw = Ledewire.init({ apiKey: 'your_api_key' })\n * const state = await lw.checkout.state('content-id')\n * </script>\n * ```\n *\n * @example npm usage:\n * ```ts\n * import { init } from '@ledewire/browser'\n * const lw = init({ apiKey: 'your_api_key' })\n * ```\n */\nexport function init(config: BrowserClientConfig): BrowserClient {\n const baseUrl = config.baseUrl ?? 'https://api.ledewire.com'\n const storage = config.storage ?? new MemoryTokenStorage()\n\n const tokenManager = new TokenManager({\n storage,\n refreshFn: async (refreshToken) => {\n const res = await fetch(`${baseUrl}/v1/auth/token/refresh`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ refresh_token: refreshToken }),\n })\n if (!res.ok) throw new Error('Token refresh failed')\n const data = (await res.json()) as {\n access_token: string\n refresh_token: string\n expires_at: string\n }\n return {\n accessToken: data.access_token,\n refreshToken: data.refresh_token,\n expiresAt: parseExpiresAt(data.expires_at),\n }\n },\n ...(config.onAuthExpired !== undefined && { onAuthExpired: config.onAuthExpired }),\n })\n\n const http = new HttpClient({\n baseUrl,\n getAccessToken: () => tokenManager.getAccessToken(),\n onUnauthorized: () => tokenManager.handleUnauthorized(),\n })\n\n return new BrowserClient(http, tokenManager, config)\n}\n\n/**\n * The LedeWire browser client.\n * Access buyer flows through the namespaced properties.\n *\n * @remarks\n * Instantiate with {@link init} rather than constructing directly.\n */\nexport class BrowserClient {\n /** Platform-level public configuration (no auth required) */\n readonly config: BrowserConfigNamespace\n\n /** Buyer authentication: email/password signup/login, Google, password reset */\n readonly auth: BrowserAuthNamespace\n\n /** Checkout state machine: determines next action for a piece of content */\n readonly checkout: CheckoutNamespace\n\n /** Wallet: balance, fund via payment session, transaction history */\n readonly wallet: BrowserWalletNamespace\n\n /** Content purchases for the authenticated buyer */\n readonly purchases: BrowserPurchasesNamespace\n\n /** Public content with per-user access information */\n readonly content: BrowserContentNamespace\n\n /** Seller content: list, search, and get (requires API key view token) */\n readonly seller: BrowserSellerNamespace\n\n /** @internal */\n constructor(\n public readonly _http: HttpClient,\n public readonly _tokenManager: TokenManager,\n public readonly _config: BrowserClientConfig,\n ) {\n this.config = new BrowserConfigNamespace(_http)\n this.auth = new BrowserAuthNamespace(_http, _tokenManager)\n this.checkout = new CheckoutNamespace(_http)\n this.wallet = new BrowserWalletNamespace(_http)\n this.purchases = new BrowserPurchasesNamespace(_http)\n this.content = new BrowserContentNamespace(_http)\n this.seller = new BrowserSellerNamespace(_http)\n }\n}\n","import type { TokenStorage, StoredTokens } from '@ledewire/core'\n\nconst DEFAULT_STORAGE_KEY = 'lw:tokens'\n\n/**\n * A {@link TokenStorage} adapter that persists tokens in `localStorage`.\n *\n * Tokens survive page reloads and browser restarts, which is convenient\n * for sites where users expect persistent sessions.\n *\n * **Security note:** `localStorage` is accessible to any JavaScript on the\n * same origin. Only use this adapter on sites you fully control and trust.\n * The default in-memory storage is safer for high-security use cases.\n *\n * @param key - The `localStorage` key used to store tokens.\n * Defaults to `'lw:tokens'`. Override this if you have multiple\n * LedeWire integrations on the same origin.\n *\n * @example\n * ```ts\n * import { init, localStorageAdapter } from '@ledewire/browser'\n *\n * const lw = init({\n * apiKey: 'your_api_key',\n * storage: localStorageAdapter(),\n * })\n * ```\n */\nexport function localStorageAdapter(key = DEFAULT_STORAGE_KEY): TokenStorage {\n return {\n getTokens(): StoredTokens | null {\n const raw = localStorage.getItem(key)\n if (!raw) return null\n try {\n return JSON.parse(raw) as StoredTokens\n } catch {\n // Corrupted value - treat as empty\n return null\n }\n },\n setTokens(tokens: StoredTokens): void {\n localStorage.setItem(key, JSON.stringify(tokens))\n },\n clearTokens(): void {\n localStorage.removeItem(key)\n },\n }\n}\n"],"names":["LedewireError","message","statusCode","code","__publicField","AuthError","ForbiddenError","NotFoundError","PurchaseError","DEFAULT_BASE_URL","HttpClient","config","path","params","body","url","method","isRetry","token","headers","response","REFRESH_THRESHOLD_MS","TokenManager","options","stored","refreshed","tokens","refreshToken","_err","MemoryTokenStorage","parseExpiresAt","expiresAt","BrowserAuthNamespace","http","tokenManager","res","BrowserConfigNamespace","BrowserContentNamespace","id","userId","BrowserPurchasesNamespace","BrowserSellerContentNamespace","BrowserSellerNamespace","BrowserWalletNamespace","sessionId","CheckoutNamespace","contentId","init","baseUrl","storage","data","BrowserClient","_http","_tokenManager","_config","DEFAULT_STORAGE_KEY","localStorageAdapter","key","raw"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- var Ledewire=(function(o){"use strict";var O=Object.defineProperty;var R=(o,a,c)=>a in o?O(o,a,{enumerable:!0,configurable:!0,writable:!0,value:c}):o[a]=c;var n=(o,a,c)=>R(o,typeof a!="symbol"?a+"":a,c);class a extends Error{constructor(t,r,i){super(t);n(this,"statusCode");n(this,"code");this.name="LedewireError",this.statusCode=r,this.code=i,Object.setPrototypeOf(this,new.target.prototype)}}class c extends a{constructor(e,t){super(e,401,t),this.name="AuthError"}}class d extends a{constructor(e,t){super(e,403,t),this.name="ForbiddenError"}}class p extends a{constructor(e,t){super(e,404,t),this.name="NotFoundError"}}class k extends a{constructor(e,t,r){super(e,t,r),this.name="PurchaseError"}}const f="https://api.ledewire.com";class T{constructor(e={}){n(this,"baseUrl");n(this,"getAccessToken");n(this,"onUnauthorized");this.baseUrl=(e.baseUrl??f).replace(/\/$/,""),this.getAccessToken=e.getAccessToken??(()=>null),this.onUnauthorized=e.onUnauthorized??(()=>null)}async get(e,t){return this.request("GET",this.buildUrl(e,t))}async post(e,t){return this.request("POST",this.buildUrl(e),t)}async put(e,t){return this.request("PUT",this.buildUrl(e),t)}async patch(e,t){return this.request("PATCH",this.buildUrl(e),t)}async delete(e){return this.request("DELETE",this.buildUrl(e))}buildUrl(e,t){const r=`${this.baseUrl}${e}`;return!t||Object.keys(t).length===0?r:`${r}?${new URLSearchParams(t).toString()}`}async request(e,t,r,i=!1){const l=await this.getAccessToken(),u={"Content-Type":"application/json",Accept:"application/json"};l&&(u.Authorization=`Bearer ${l}`);const h=await fetch(t,{method:e,headers:u,...r!==void 0&&{body:JSON.stringify(r)}});if(h.status===401&&!i){if(await this.onUnauthorized())return this.request(e,t,r,!0);throw new c("Session expired. Please re-authenticate.")}if(h.ok||await this.throwApiError(h),h.status!==204)return h.json()}async throwApiError(e){let t;try{t=await e.json()}catch{}const r=t?.error.message??e.statusText,i=t?.error.code;switch(e.status){case 401:throw new c(r,i);case 403:throw new d(r,i);case 404:throw new p(r,i);default:throw new a(r,e.status,i)}}}const y=6e4;class E{constructor(e){n(this,"storage");n(this,"refreshFn");n(this,"onTokenRefreshed");n(this,"onAuthExpired");n(this,"refreshPromise",null);this.storage=e.storage,this.refreshFn=e.refreshFn,this.onTokenRefreshed=e.onTokenRefreshed,this.onAuthExpired=e.onAuthExpired}async getAccessToken(){const e=await this.storage.getTokens();return e?e.expiresAt-Date.now()<y?(await this.performRefresh(e.refreshToken))?.accessToken??null:e.accessToken:null}async handleUnauthorized(){const e=await this.storage.getTokens();if(!e)return await this.onAuthExpired?.(),null;const t=await this.performRefresh(e.refreshToken);return t||await this.onAuthExpired?.(),t?.accessToken??null}async setTokens(e){await this.storage.setTokens(e)}async clearTokens(){await this.storage.clearTokens()}performRefresh(e){return this.refreshPromise?this.refreshPromise:(this.refreshPromise=this.refreshFn(e).then(async t=>(await this.storage.setTokens(t),await this.onTokenRefreshed?.(t),t)).catch(t=>{throw this.storage.clearTokens(),new c("Token refresh failed. Please re-authenticate.")}).finally(()=>{this.refreshPromise=null}),this.refreshPromise)}}class g{constructor(){n(this,"tokens",null)}getTokens(){return this.tokens}setTokens(e){this.tokens=e}clearTokens(){this.tokens=null}}function w(s){return new Date(s).getTime()}class m{constructor(e,t){this.http=e,this.tokenManager=t}async signup(e){const t=await this.http.post("/v1/auth/signup",e);return await this.storeTokens(t),t}async loginWithEmail(e){const t=await this.http.post("/v1/auth/login/email",e);return await this.storeTokens(t),t}async loginWithGoogle(e){const t=await this.http.post("/v1/auth/login/google",e);return await this.storeTokens(t),t}async storeTokens(e){await this.tokenManager.setTokens({accessToken:e.access_token,refreshToken:e.refresh_token,expiresAt:w(e.expires_at)})}}class A{constructor(e){this.http=e}async getPublic(){return this.http.get("/v1/config/public")}}class b{constructor(e){this.http=e}async getWithAccess(e,t){const r={};return t!==void 0&&(r.user_id=t),this.http.get(`/v1/content/${e}/with-access`,Object.keys(r).length>0?r:void 0)}}class S{constructor(e){this.http=e}async create(e){return this.http.post("/v1/purchases",e)}async list(){return this.http.get("/v1/purchases")}async get(e){return this.http.get(`/v1/purchases/${e}`)}}class U{constructor(e){this.http=e}async balance(){return this.http.get("/v1/wallet/balance")}async transactions(){return this.http.get("/v1/wallet/transactions")}async createPaymentSession(e){return this.http.post("/v1/wallet/payment-session",e)}async getPaymentStatus(e){return this.http.get(`/v1/wallet/payment-status/${e}`)}}class v{constructor(e){this.http=e}async state(e){return this.http.get(`/v1/checkout/state/${e}`)}}function P(s){const e=s.baseUrl??"https://api.ledewire.com",t=s.storage??new g,r=new E({storage:t,refreshFn:async l=>{const u=await fetch(`${e}/v1/auth/token/refresh`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refresh_token:l})});if(!u.ok)throw new Error("Token refresh failed");const h=await u.json();return{accessToken:h.access_token,refreshToken:h.refresh_token,expiresAt:w(h.expires_at)}},...s.onAuthExpired!==void 0&&{onAuthExpired:s.onAuthExpired}}),i=new T({baseUrl:e,getAccessToken:()=>r.getAccessToken(),onUnauthorized:()=>r.handleUnauthorized()});return new x(i,r,s)}class x{constructor(e,t,r){n(this,"config");n(this,"auth");n(this,"checkout");n(this,"wallet");n(this,"purchases");n(this,"content");this._http=e,this._tokenManager=t,this._config=r,this.config=new A(e),this.auth=new m(e,t),this.checkout=new v(e),this.wallet=new U(e),this.purchases=new S(e),this.content=new b(e)}}const F="lw:tokens";function N(s=F){return{getTokens(){const e=localStorage.getItem(s);if(!e)return null;try{return JSON.parse(e)}catch{return null}},setTokens(e){localStorage.setItem(s,JSON.stringify(e))},clearTokens(){localStorage.removeItem(s)}}}return o.AuthError=c,o.ForbiddenError=d,o.LedewireError=a,o.MemoryTokenStorage=g,o.NotFoundError=p,o.PurchaseError=k,o.init=P,o.localStorageAdapter=N,o.parseExpiresAt=w,Object.defineProperty(o,Symbol.toStringTag,{value:"Module"}),o})({});
1
+ var Ledewire=(function(o){"use strict";var $=Object.defineProperty;var C=(o,a,c)=>a in o?$(o,a,{enumerable:!0,configurable:!0,writable:!0,value:c}):o[a]=c;var n=(o,a,c)=>C(o,typeof a!="symbol"?a+"":a,c);class a extends Error{constructor(t,r,i){super(t);n(this,"statusCode");n(this,"code");this.name="LedewireError",this.statusCode=r,this.code=i,Object.setPrototypeOf(this,new.target.prototype)}}class c extends a{constructor(e,t){super(e,401,t),this.name="AuthError"}}class w extends a{constructor(e,t){super(e,403,t),this.name="ForbiddenError"}}class d extends a{constructor(e,t){super(e,404,t),this.name="NotFoundError"}}class k extends a{constructor(e,t,r){super(e,t,r),this.name="PurchaseError"}}const f="https://api.ledewire.com";class T{constructor(e={}){n(this,"baseUrl");n(this,"getAccessToken");n(this,"onUnauthorized");this.baseUrl=(e.baseUrl??f).replace(/\/$/,""),this.getAccessToken=e.getAccessToken??(()=>null),this.onUnauthorized=e.onUnauthorized??(()=>null)}async get(e,t){return this.request("GET",this.buildUrl(e,t))}async post(e,t){return this.request("POST",this.buildUrl(e),t)}async put(e,t){return this.request("PUT",this.buildUrl(e),t)}async patch(e,t){return this.request("PATCH",this.buildUrl(e),t)}async delete(e){return this.request("DELETE",this.buildUrl(e))}buildUrl(e,t){const r=`${this.baseUrl}${e}`;return!t||Object.keys(t).length===0?r:`${r}?${new URLSearchParams(t).toString()}`}async request(e,t,r,i=!1){const l=await this.getAccessToken(),u={"Content-Type":"application/json",Accept:"application/json"};l&&(u.Authorization=`Bearer ${l}`);const h=await fetch(t,{method:e,headers:u,...r!==void 0&&{body:JSON.stringify(r)}});if(h.status===401&&!i){if(await this.onUnauthorized())return this.request(e,t,r,!0);throw new c("Session expired. Please re-authenticate.")}if(h.ok||await this.throwApiError(h),h.status!==204)return h.json()}async throwApiError(e){let t;try{t=await e.json()}catch{}const r=t?.error.message??e.statusText,i=t?.error.code;switch(e.status){case 401:throw new c(r,i);case 403:throw new w(r,i);case 404:throw new d(r,i);default:throw new a(r,e.status,i)}}}const y=6e4;class E{constructor(e){n(this,"storage");n(this,"refreshFn");n(this,"onTokenRefreshed");n(this,"onAuthExpired");n(this,"refreshPromise",null);this.storage=e.storage,this.refreshFn=e.refreshFn,this.onTokenRefreshed=e.onTokenRefreshed,this.onAuthExpired=e.onAuthExpired}async getAccessToken(){const e=await this.storage.getTokens();return e?e.expiresAt-Date.now()<y?(await this.performRefresh(e.refreshToken))?.accessToken??null:e.accessToken:null}async handleUnauthorized(){const e=await this.storage.getTokens();if(!e)return await this.onAuthExpired?.(),null;const t=await this.performRefresh(e.refreshToken);return t||await this.onAuthExpired?.(),t?.accessToken??null}async setTokens(e){await this.storage.setTokens(e)}async clearTokens(){await this.storage.clearTokens()}performRefresh(e){return this.refreshPromise?this.refreshPromise:(this.refreshPromise=this.refreshFn(e).then(async t=>(await this.storage.setTokens(t),await this.onTokenRefreshed?.(t),t)).catch(t=>{throw this.storage.clearTokens(),new c("Token refresh failed. Please re-authenticate.")}).finally(()=>{this.refreshPromise=null}),this.refreshPromise)}}class g{constructor(){n(this,"tokens",null)}getTokens(){return this.tokens}setTokens(e){this.tokens=e}clearTokens(){this.tokens=null}}function p(s){return new Date(s).getTime()}class m{constructor(e,t){this.http=e,this.tokenManager=t}async signup(e){const t=await this.http.post("/v1/auth/signup",e);return await this.storeTokens(t),t}async loginWithEmail(e){const t=await this.http.post("/v1/auth/login/email",e);return await this.storeTokens(t),t}async loginWithGoogle(e){const t=await this.http.post("/v1/auth/login/google",e);return await this.storeTokens(t),t}async loginWithApiKey(e){const t=await this.http.post("/v1/auth/login/api-key",e);return await this.storeTokens(t),t}async storeTokens(e){await this.tokenManager.setTokens({accessToken:e.access_token,refreshToken:e.refresh_token,expiresAt:p(e.expires_at)})}}class A{constructor(e){this.http=e}async getPublic(){return this.http.get("/v1/config/public")}}class v{constructor(e){this.http=e}async getWithAccess(e,t){const r={};return t!==void 0&&(r.user_id=t),this.http.get(`/v1/content/${e}/with-access`,Object.keys(r).length>0?r:void 0)}}class S{constructor(e){this.http=e}async create(e){return this.http.post("/v1/purchases",e)}async list(){return this.http.get("/v1/purchases")}async get(e){return this.http.get(`/v1/purchases/${e}`)}}class b{constructor(e){this.http=e}async list(){return this.http.get("/v1/seller/content")}async search(e){return this.http.post("/v1/seller/content/search",e)}async get(e){return this.http.get(`/v1/seller/content/${e}`)}}class U{constructor(e){n(this,"content");this.content=new b(e)}}class P{constructor(e){this.http=e}async balance(){return this.http.get("/v1/wallet/balance")}async transactions(){return this.http.get("/v1/wallet/transactions")}async createPaymentSession(e){return this.http.post("/v1/wallet/payment-session",e)}async getPaymentStatus(e){return this.http.get(`/v1/wallet/payment-status/${e}`)}}class N{constructor(e){this.http=e}async state(e){return this.http.get(`/v1/checkout/state/${e}`)}}function x(s){const e=s.baseUrl??"https://api.ledewire.com",t=s.storage??new g,r=new E({storage:t,refreshFn:async l=>{const u=await fetch(`${e}/v1/auth/token/refresh`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refresh_token:l})});if(!u.ok)throw new Error("Token refresh failed");const h=await u.json();return{accessToken:h.access_token,refreshToken:h.refresh_token,expiresAt:p(h.expires_at)}},...s.onAuthExpired!==void 0&&{onAuthExpired:s.onAuthExpired}}),i=new T({baseUrl:e,getAccessToken:()=>r.getAccessToken(),onUnauthorized:()=>r.handleUnauthorized()});return new F(i,r,s)}class F{constructor(e,t,r){n(this,"config");n(this,"auth");n(this,"checkout");n(this,"wallet");n(this,"purchases");n(this,"content");n(this,"seller");this._http=e,this._tokenManager=t,this._config=r,this.config=new A(e),this.auth=new m(e,t),this.checkout=new N(e),this.wallet=new P(e),this.purchases=new S(e),this.content=new v(e),this.seller=new U(e)}}const O="lw:tokens";function R(s=O){return{getTokens(){const e=localStorage.getItem(s);if(!e)return null;try{return JSON.parse(e)}catch{return null}},setTokens(e){localStorage.setItem(s,JSON.stringify(e))},clearTokens(){localStorage.removeItem(s)}}}return o.AuthError=c,o.ForbiddenError=w,o.LedewireError=a,o.MemoryTokenStorage=g,o.NotFoundError=d,o.PurchaseError=k,o.init=x,o.localStorageAdapter=R,o.parseExpiresAt=p,Object.defineProperty(o,Symbol.toStringTag,{value:"Module"}),o})({});
2
2
  //# sourceMappingURL=ledewire.min.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ledewire.min.js","sources":["../../core/src/errors.ts","../../core/src/http-client.ts","../../core/src/token-manager.ts","../src/resources/auth.ts","../src/resources/config.ts","../src/resources/content.ts","../src/resources/purchases.ts","../src/resources/wallet.ts","../src/resources/checkout.ts","../src/client.ts","../src/local-storage-adapter.ts"],"sourcesContent":["/**\n * Base error class for all LedeWire SDK errors.\n * All errors thrown by the SDK are instances of this class,\n * making it safe to use `err instanceof LedewireError` as a type guard.\n *\n * @example\n * ```ts\n * try {\n * await client.purchases.create({ contentId: '...' })\n * } catch (err) {\n * if (err instanceof LedewireError) {\n * console.error(err.statusCode, err.message)\n * }\n * }\n * ```\n */\nexport class LedewireError extends Error {\n /** HTTP status code returned by the API (e.g. 400, 401, 404, 422). */\n public readonly statusCode: number\n\n /** Machine-readable error code from the API error body, if present. */\n public readonly code: number | undefined\n\n constructor(message: string, statusCode: number, code?: number) {\n super(message)\n this.name = 'LedewireError'\n this.statusCode = statusCode\n this.code = code\n // Restore prototype chain (required for extending built-in Error in TS)\n Object.setPrototypeOf(this, new.target.prototype)\n }\n}\n\n/**\n * Thrown when the request lacks valid authentication credentials,\n * or when a token refresh fails and re-authentication is required.\n */\nexport class AuthError extends LedewireError {\n constructor(message: string, code?: number) {\n super(message, 401, code)\n this.name = 'AuthError'\n }\n}\n\n/**\n * Thrown when the authenticated user does not have permission\n * to perform the requested operation.\n */\nexport class ForbiddenError extends LedewireError {\n constructor(message: string, code?: number) {\n super(message, 403, code)\n this.name = 'ForbiddenError'\n }\n}\n\n/**\n * Thrown when the requested resource does not exist.\n */\nexport class NotFoundError extends LedewireError {\n constructor(message: string, code?: number) {\n super(message, 404, code)\n this.name = 'NotFoundError'\n }\n}\n\n/**\n * Thrown when the purchase cannot be completed due to a validation\n * failure, such as a price mismatch or a duplicate purchase.\n */\nexport class PurchaseError extends LedewireError {\n constructor(message: string, statusCode: number, code?: number) {\n super(message, statusCode, code)\n this.name = 'PurchaseError'\n }\n}\n","import type { components } from './api.gen.js'\nimport { AuthError, ForbiddenError, LedewireError, NotFoundError } from './errors.js'\n\n/** @internal */\ntype ApiErrorBody = components['schemas']['ErrorResponse']\n\n/**\n * Configuration options for `HttpClient`.\n */\nexport interface HttpClientConfig {\n /** API base URL. Defaults to the production API. */\n baseUrl?: string\n /**\n * Returns the current access token (or null) before each request.\n * If null, requests are sent without an Authorization header.\n */\n getAccessToken?: () => string | null | Promise<string | null>\n /**\n * Called when a 401 is received on the first attempt.\n * Should refresh and return the new access token, or null to propagate\n * an `AuthError` to the caller.\n */\n onUnauthorized?: () => string | null | Promise<string | null>\n}\n\nconst DEFAULT_BASE_URL = 'https://api.ledewire.com'\n\n/**\n * Core HTTP client used by all SDK packages.\n *\n * Features:\n * - Injects `Authorization: Bearer <token>` headers automatically\n * - Maps HTTP error responses to typed `LedewireError` subclasses\n * - On receiving a 401, calls `onUnauthorized` once and retries the request\n *\n * This is an internal class - consumers should use the\n * package-level client factories (`init` / `createClient`) instead.\n */\nexport class HttpClient {\n private readonly baseUrl: string\n private readonly getAccessToken: NonNullable<HttpClientConfig['getAccessToken']>\n private readonly onUnauthorized: NonNullable<HttpClientConfig['onUnauthorized']>\n\n constructor(config: HttpClientConfig = {}) {\n this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, '')\n this.getAccessToken = config.getAccessToken ?? (() => null)\n this.onUnauthorized = config.onUnauthorized ?? (() => null)\n }\n\n /**\n * GET request with optional query parameters.\n * @param path - API path (e.g. `/v1/wallet/balance`)\n * @param params - Query string parameters\n */\n async get<T>(path: string, params?: Record<string, string>): Promise<T> {\n return this.request<T>('GET', this.buildUrl(path, params))\n }\n\n /**\n * POST request.\n * @param path - API path\n * @param body - Request body (JSON-serialized)\n */\n async post<T>(path: string, body?: unknown): Promise<T> {\n return this.request<T>('POST', this.buildUrl(path), body)\n }\n\n /**\n * PUT request.\n * @param path - API path\n * @param body - Request body (JSON-serialized)\n */\n async put<T>(path: string, body?: unknown): Promise<T> {\n return this.request<T>('PUT', this.buildUrl(path), body)\n }\n\n /**\n * PATCH request.\n * @param path - API path\n * @param body - Partial update body (JSON-serialized)\n */\n async patch<T>(path: string, body?: unknown): Promise<T> {\n return this.request<T>('PATCH', this.buildUrl(path), body)\n }\n\n /**\n * DELETE request.\n * @param path - API path\n */\n async delete<T = void>(path: string): Promise<T> {\n return this.request<T>('DELETE', this.buildUrl(path))\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private buildUrl(path: string, params?: Record<string, string>): string {\n const url = `${this.baseUrl}${path}`\n if (!params || Object.keys(params).length === 0) return url\n return `${url}?${new URLSearchParams(params).toString()}`\n }\n\n private async request<T>(\n method: string,\n url: string,\n body?: unknown,\n isRetry = false,\n ): Promise<T> {\n const token = await this.getAccessToken()\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n }\n if (token) headers['Authorization'] = `Bearer ${token}`\n\n const response = await fetch(url, {\n method,\n headers,\n ...(body !== undefined && { body: JSON.stringify(body) }),\n })\n\n if (response.status === 401 && !isRetry) {\n const newToken = await this.onUnauthorized()\n if (newToken) {\n return this.request<T>(method, url, body, true)\n }\n throw new AuthError('Session expired. Please re-authenticate.')\n }\n\n if (!response.ok) {\n await this.throwApiError(response)\n }\n\n if (response.status === 204) {\n return undefined as T\n }\n\n return response.json() as Promise<T>\n }\n\n private async throwApiError(response: Response): Promise<never> {\n let body: ApiErrorBody | undefined\n try {\n body = (await response.json()) as ApiErrorBody\n } catch {\n // Non-JSON body - fall through to use statusText\n }\n\n const message = body?.error.message ?? response.statusText\n const code = body?.error.code\n\n switch (response.status) {\n case 401:\n throw new AuthError(message, code)\n case 403:\n throw new ForbiddenError(message, code)\n case 404:\n throw new NotFoundError(message, code)\n default:\n throw new LedewireError(message, response.status, code)\n }\n }\n}\n","import type { StoredTokens, TokenStorage } from './types.js'\nimport { AuthError } from './errors.js'\n\n/** How many milliseconds before expiry to proactively refresh the token. */\nconst REFRESH_THRESHOLD_MS = 60_000 // 60 seconds\n\n/**\n * Options for constructing a `TokenManager`.\n */\nexport interface TokenManagerOptions {\n storage: TokenStorage\n /**\n * Exchanges a refresh token for a new token pair.\n * Implemented by each package's client factory using the\n * `/v1/auth/token/refresh` endpoint.\n */\n refreshFn: (refreshToken: string) => Promise<StoredTokens>\n /**\n * Called after a successful token refresh.\n * Use in server environments to persist the new tokens to a database or cache.\n *\n * @example\n * ```ts\n * onTokenRefreshed: async (tokens) => {\n * await redis.set('session:tokens', JSON.stringify(tokens))\n * }\n * ```\n */\n onTokenRefreshed?: (tokens: StoredTokens) => void | Promise<void>\n /**\n * Called when a refresh attempt fails and the user must re-authenticate.\n * Typically used to redirect to login or emit a UI event.\n *\n * @example\n * ```ts\n * onAuthExpired: () => { window.location.href = '/login' }\n * ```\n */\n onAuthExpired?: () => void | Promise<void>\n}\n\n/**\n * Manages the JWT token lifecycle transparently.\n *\n * - Proactively refreshes the access token when it expires within 60 seconds\n * - Handles reactive refresh on 401 responses from the HTTP client\n * - Deduplicates concurrent refresh calls (only one in-flight refresh at a time)\n * - Fires `onAuthExpired` when refresh fails so the UI can prompt re-login\n *\n * This is an internal class - use the package-level client factories instead.\n */\nexport class TokenManager {\n private readonly storage: TokenStorage\n private readonly refreshFn: TokenManagerOptions['refreshFn']\n private readonly onTokenRefreshed: TokenManagerOptions['onTokenRefreshed']\n private readonly onAuthExpired: TokenManagerOptions['onAuthExpired']\n private refreshPromise: Promise<StoredTokens | null> | null = null\n\n constructor(options: TokenManagerOptions) {\n this.storage = options.storage\n this.refreshFn = options.refreshFn\n this.onTokenRefreshed = options.onTokenRefreshed\n this.onAuthExpired = options.onAuthExpired\n }\n\n /**\n * Returns a valid access token, refreshing proactively if needed.\n * Returns `null` if no tokens are stored (user not authenticated).\n */\n async getAccessToken(): Promise<string | null> {\n const stored = await this.storage.getTokens()\n if (!stored) return null\n\n const expiresInMs = stored.expiresAt - Date.now()\n if (expiresInMs < REFRESH_THRESHOLD_MS) {\n const refreshed = await this.performRefresh(stored.refreshToken)\n return refreshed?.accessToken ?? null\n }\n\n return stored.accessToken\n }\n\n /**\n * Called by `HttpClient` when a 401 is received.\n * Attempts one refresh; returns the new access token or null.\n */\n async handleUnauthorized(): Promise<string | null> {\n const stored = await this.storage.getTokens()\n if (!stored) {\n await this.onAuthExpired?.()\n return null\n }\n const refreshed = await this.performRefresh(stored.refreshToken)\n if (!refreshed) await this.onAuthExpired?.()\n return refreshed?.accessToken ?? null\n }\n\n /** Store new tokens after a successful login or signup. */\n async setTokens(tokens: StoredTokens): Promise<void> {\n await this.storage.setTokens(tokens)\n }\n\n /** Clear all stored tokens (call on logout). */\n async clearTokens(): Promise<void> {\n await this.storage.clearTokens()\n }\n\n // ---------------------------------------------------------------------------\n // Private\n // ---------------------------------------------------------------------------\n\n private performRefresh(refreshToken: string): Promise<StoredTokens | null> {\n if (this.refreshPromise) return this.refreshPromise\n\n this.refreshPromise = this.refreshFn(refreshToken)\n .then(async (tokens) => {\n await this.storage.setTokens(tokens)\n await this.onTokenRefreshed?.(tokens)\n return tokens\n })\n .catch((_err: unknown) => {\n void this.storage.clearTokens()\n throw new AuthError('Token refresh failed. Please re-authenticate.')\n })\n .finally(() => {\n this.refreshPromise = null\n })\n\n return this.refreshPromise\n }\n}\n\n// ---------------------------------------------------------------------------\n// Built-in storage adapters\n// ---------------------------------------------------------------------------\n\n/**\n * In-memory token storage (default for both packages).\n * Tokens are cleared when the page unloads or the process exits.\n * This is the most secure default for browser environments.\n */\nexport class MemoryTokenStorage implements TokenStorage {\n private tokens: StoredTokens | null = null\n\n getTokens(): StoredTokens | null {\n return this.tokens\n }\n\n setTokens(tokens: StoredTokens): void {\n this.tokens = tokens\n }\n\n clearTokens(): void {\n this.tokens = null\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Converts an `expires_at` ISO 8601 string from an API auth response\n * into a Unix timestamp (milliseconds) for use in `StoredTokens.expiresAt`.\n *\n * @param expiresAt - ISO 8601 datetime string, e.g. `\"2026-01-01T12:00:00Z\"`\n * @returns Unix timestamp in milliseconds\n */\nexport function parseExpiresAt(expiresAt: string): number {\n return new Date(expiresAt).getTime()\n}\n","/**\n * Buyer authentication namespace for browser environments.\n *\n * Tokens are stored automatically in the `TokenManager` after every\n * successful call — callers never need to manage tokens directly.\n *\n * @module\n */\nimport { parseExpiresAt } from '@ledewire/core'\nimport type { HttpClient, TokenManager } from '@ledewire/core'\nimport type {\n AuthLoginEmailRequest,\n AuthLoginOAuthRequest,\n AuthSignupRequest,\n AuthenticationResponse,\n} from '@ledewire/core'\n\n/**\n * Buyer authentication: signup, email/password login, Google OAuth.\n *\n * Obtain via `lw.auth` — do not construct directly.\n *\n * @example\n * ```ts\n * const lw = Ledewire.init({ apiKey: 'your_api_key' })\n * await lw.auth.loginWithEmail({ email: 'u@example.com', password: 'pw' })\n * ```\n */\nexport class BrowserAuthNamespace {\n /** @internal */\n constructor(\n private readonly http: HttpClient,\n private readonly tokenManager: TokenManager,\n ) {}\n\n /**\n * Register a new buyer account with email and password.\n * Tokens are stored automatically after successful signup.\n *\n * @param body - Signup credentials and display name.\n * @returns The authentication token response.\n */\n async signup(body: AuthSignupRequest): Promise<AuthenticationResponse> {\n const res = await this.http.post<AuthenticationResponse>('/v1/auth/signup', body)\n await this.storeTokens(res)\n return res\n }\n\n /**\n * Log in with email and password.\n * Tokens are stored automatically after successful login.\n *\n * @param body - Email and password credentials.\n * @returns The authentication token response.\n */\n async loginWithEmail(body: AuthLoginEmailRequest): Promise<AuthenticationResponse> {\n const res = await this.http.post<AuthenticationResponse>('/v1/auth/login/email', body)\n await this.storeTokens(res)\n return res\n }\n\n /**\n * Log in with a Google ID token obtained from the Google OAuth flow.\n * Tokens are stored automatically after successful login.\n *\n * @param body - The Google ID token.\n * @returns The authentication token response.\n */\n async loginWithGoogle(body: AuthLoginOAuthRequest): Promise<AuthenticationResponse> {\n const res = await this.http.post<AuthenticationResponse>('/v1/auth/login/google', body)\n await this.storeTokens(res)\n return res\n }\n\n private async storeTokens(res: AuthenticationResponse): Promise<void> {\n await this.tokenManager.setTokens({\n accessToken: res.access_token,\n refreshToken: res.refresh_token,\n expiresAt: parseExpiresAt(res.expires_at),\n })\n }\n}\n","/**\n * Platform configuration namespace (browser).\n *\n * Provides access to public (unauthenticated) platform-level configuration.\n *\n * @module\n */\nimport type { HttpClient } from '@ledewire/core'\nimport type { PublicConfigResponse } from '@ledewire/core'\n\n/**\n * Platform-level configuration for browser clients.\n *\n * Obtain via `client.config` — do not construct directly.\n */\nexport class BrowserConfigNamespace {\n /** @internal */\n constructor(protected readonly http: HttpClient) {}\n\n /**\n * Returns platform-level public configuration.\n * No authentication required — safe to call before the user has signed in.\n *\n * Use `google_client_id` to initialise the Google Identity Services library\n * on the storefront login page without requiring a prior authenticated call.\n *\n * @example\n * ```ts\n * const lw = init({ apiKey: 'your_api_key' })\n * const { google_client_id } = await lw.config.getPublic()\n * google.accounts.id.initialize({ client_id: google_client_id, callback })\n * ```\n */\n async getPublic(): Promise<PublicConfigResponse> {\n return this.http.get<PublicConfigResponse>('/v1/config/public')\n }\n}\n","import type { HttpClient } from '@ledewire/core'\nimport type { ContentWithAccessResponse } from '@ledewire/core'\n\n/**\n * Buyer content namespace — fetch content with real-time access information.\n *\n * Obtain via `lw.content` — do not construct directly.\n *\n * @example\n * ```ts\n * const { content, access } = await lw.content.getWithAccess('content-id')\n * if (access.has_access) showFullContent(content)\n * ```\n */\nexport class BrowserContentNamespace {\n constructor(protected readonly http: HttpClient) {}\n\n /**\n * Returns a content item together with access information for the\n * authenticated buyer (or a generic access response when unauthenticated).\n *\n * @param id - The content ID.\n * @param userId - Optional user ID to check a specific user's access status.\n * @returns The content item with access information.\n */\n async getWithAccess(id: string, userId?: string): Promise<ContentWithAccessResponse> {\n const params: Record<string, string> = {}\n if (userId !== undefined) {\n params['user_id'] = userId\n }\n return this.http.get<ContentWithAccessResponse>(\n `/v1/content/${id}/with-access`,\n Object.keys(params).length > 0 ? params : undefined,\n )\n }\n}\n","import type { HttpClient } from '@ledewire/core'\nimport type { PurchaseCreateRequest, PurchaseResponse } from '@ledewire/core'\n\n/**\n * Buyer purchases namespace — create and retrieve content purchases.\n *\n * Obtain via `lw.purchases` — do not construct directly.\n *\n * @example\n * ```ts\n * await lw.purchases.create({ content_id: 'content-id' })\n * const all = await lw.purchases.list()\n * ```\n */\nexport class BrowserPurchasesNamespace {\n constructor(protected readonly http: HttpClient) {}\n\n /**\n * Completes a content purchase using the buyer's wallet balance.\n *\n * @param body - The content ID and expected price in cents.\n * @returns The completed purchase record.\n */\n async create(body: PurchaseCreateRequest): Promise<PurchaseResponse> {\n return this.http.post<PurchaseResponse>('/v1/purchases', body)\n }\n\n /**\n * Returns all purchases made by the authenticated buyer.\n *\n * @returns A list of purchase records, newest first.\n */\n async list(): Promise<PurchaseResponse[]> {\n return this.http.get<PurchaseResponse[]>('/v1/purchases')\n }\n\n /**\n * Returns a single purchase by ID.\n *\n * @param id - The purchase ID.\n * @returns The purchase record.\n */\n async get(id: string): Promise<PurchaseResponse> {\n return this.http.get<PurchaseResponse>(`/v1/purchases/${id}`)\n }\n}\n","import type { HttpClient } from '@ledewire/core'\nimport type {\n WalletBalanceResponse,\n WalletPaymentSessionRequest,\n WalletPaymentSessionResponse,\n WalletPaymentStatusResponse,\n WalletTransactionItem,\n} from '@ledewire/core'\n\n/**\n * Buyer wallet namespace — balance, transactions, and payment session management.\n *\n * Obtain via `lw.wallet` — do not construct directly.\n *\n * @example\n * ```ts\n * const { balance_cents } = await lw.wallet.balance()\n * const session = await lw.wallet.createPaymentSession({ amount_cents: 500 })\n * ```\n */\nexport class BrowserWalletNamespace {\n constructor(protected readonly http: HttpClient) {}\n\n /**\n * Returns the authenticated buyer's current wallet balance.\n *\n * @returns The current wallet balance in cents.\n */\n async balance(): Promise<WalletBalanceResponse> {\n return this.http.get<WalletBalanceResponse>('/v1/wallet/balance')\n }\n\n /**\n * Returns the authenticated buyer's wallet transaction history, newest first.\n *\n * @returns A list of completed wallet transaction entries.\n */\n async transactions(): Promise<WalletTransactionItem[]> {\n return this.http.get<WalletTransactionItem[]>('/v1/wallet/transactions')\n }\n\n /**\n * Creates a payment session for funding the buyer's wallet.\n *\n * @param body - The amount and currency to fund.\n * @returns Payment session details for use with the payment provider widget.\n */\n async createPaymentSession(\n body: WalletPaymentSessionRequest,\n ): Promise<WalletPaymentSessionResponse> {\n return this.http.post<WalletPaymentSessionResponse>('/v1/wallet/payment-session', body)\n }\n\n /**\n * Polls the status of a wallet funding payment session.\n *\n * @param sessionId - The session ID returned by `createPaymentSession`.\n * @returns The current payment status.\n */\n async getPaymentStatus(sessionId: string): Promise<WalletPaymentStatusResponse> {\n return this.http.get<WalletPaymentStatusResponse>(`/v1/wallet/payment-status/${sessionId}`)\n }\n}\n","import type { HttpClient } from '@ledewire/core'\nimport type { CheckoutStateResponse } from '@ledewire/core'\n\n/**\n * Checkout namespace — buyer checkout state for a specific content item.\n *\n * Obtain via `lw.checkout` — do not construct directly.\n *\n * @example\n * ```ts\n * const state = await lw.checkout.state('content-id')\n * // state.checkout_state.next_required_action:\n * // 'authenticate' | 'fund_wallet' | 'purchase' | 'view_content'\n * ```\n */\nexport class CheckoutNamespace {\n constructor(protected readonly http: HttpClient) {}\n\n /**\n * Returns the authenticated buyer's checkout readiness for a given piece of\n * content. Indicates whether the buyer needs to authenticate, fund their\n * wallet, or purchase before accessing the content.\n *\n * Works without authentication — returns anonymous checkout state when the\n * buyer is not logged in.\n *\n * @param contentId - The content item ID.\n * @returns The checkout state, including what action is required next.\n */\n async state(contentId: string): Promise<CheckoutStateResponse> {\n return this.http.get<CheckoutStateResponse>(`/v1/checkout/state/${contentId}`)\n }\n}\n","import { HttpClient, MemoryTokenStorage, TokenManager, parseExpiresAt } from '@ledewire/core'\nimport type { TokenStorage } from '@ledewire/core'\nimport { BrowserAuthNamespace } from './resources/auth.js'\nimport { BrowserConfigNamespace } from './resources/config.js'\nimport { BrowserContentNamespace } from './resources/content.js'\nimport { BrowserPurchasesNamespace } from './resources/purchases.js'\nimport { BrowserWalletNamespace } from './resources/wallet.js'\nimport { CheckoutNamespace } from './resources/checkout.js'\n\n/**\n * Configuration options for the LedeWire browser client.\n */\nexport interface BrowserClientConfig {\n /**\n * LedeWire API key that identifies the store.\n * Obtained from the LedeWire merchant dashboard.\n */\n apiKey: string\n /**\n * Override the API base URL.\n * Defaults to `https://api.ledewire.com`.\n */\n baseUrl?: string\n /**\n * Token storage adapter.\n * Defaults to {@link MemoryTokenStorage} (in-memory, most secure).\n *\n * To persist sessions across page reloads, use the built-in\n * `localStorageAdapter`:\n * ```ts\n * import { init, localStorageAdapter } from '@ledewire/browser'\n * const lw = init({ apiKey: '...', storage: localStorageAdapter() })\n * ```\n */\n storage?: TokenStorage\n /**\n * Called when the user's session expires and cannot be refreshed.\n * Use this to show a re-authentication prompt.\n *\n * @example\n * ```ts\n * onAuthExpired: () => showLoginModal()\n * ```\n */\n onAuthExpired?: () => void\n}\n\n/**\n * Initialises a LedeWire browser client.\n * This is the primary entry point for the browser SDK.\n *\n * @example CDN usage:\n * ```html\n * <script src=\"https://cdn.jsdelivr.net/npm/@ledewire/browser@1/dist/ledewire.min.js\"></script>\n * <script>\n * const lw = Ledewire.init({ apiKey: 'your_api_key' })\n * const state = await lw.checkout.state('content-id')\n * </script>\n * ```\n *\n * @example npm usage:\n * ```ts\n * import { init } from '@ledewire/browser'\n * const lw = init({ apiKey: 'your_api_key' })\n * ```\n */\nexport function init(config: BrowserClientConfig): BrowserClient {\n const baseUrl = config.baseUrl ?? 'https://api.ledewire.com'\n const storage = config.storage ?? new MemoryTokenStorage()\n\n const tokenManager = new TokenManager({\n storage,\n refreshFn: async (refreshToken) => {\n const res = await fetch(`${baseUrl}/v1/auth/token/refresh`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ refresh_token: refreshToken }),\n })\n if (!res.ok) throw new Error('Token refresh failed')\n const data = (await res.json()) as {\n access_token: string\n refresh_token: string\n expires_at: string\n }\n return {\n accessToken: data.access_token,\n refreshToken: data.refresh_token,\n expiresAt: parseExpiresAt(data.expires_at),\n }\n },\n ...(config.onAuthExpired !== undefined && { onAuthExpired: config.onAuthExpired }),\n })\n\n const http = new HttpClient({\n baseUrl,\n getAccessToken: () => tokenManager.getAccessToken(),\n onUnauthorized: () => tokenManager.handleUnauthorized(),\n })\n\n return new BrowserClient(http, tokenManager, config)\n}\n\n/**\n * The LedeWire browser client.\n * Access buyer flows through the namespaced properties.\n *\n * @remarks\n * Instantiate with {@link init} rather than constructing directly.\n */\nexport class BrowserClient {\n /** Platform-level public configuration (no auth required) */\n readonly config: BrowserConfigNamespace\n\n /** Buyer authentication: email/password signup/login, Google, password reset */\n readonly auth: BrowserAuthNamespace\n\n /** Checkout state machine: determines next action for a piece of content */\n readonly checkout: CheckoutNamespace\n\n /** Wallet: balance, fund via payment session, transaction history */\n readonly wallet: BrowserWalletNamespace\n\n /** Content purchases for the authenticated buyer */\n readonly purchases: BrowserPurchasesNamespace\n\n /** Public content with per-user access information */\n readonly content: BrowserContentNamespace\n\n /** @internal */\n constructor(\n public readonly _http: HttpClient,\n public readonly _tokenManager: TokenManager,\n public readonly _config: BrowserClientConfig,\n ) {\n this.config = new BrowserConfigNamespace(_http)\n this.auth = new BrowserAuthNamespace(_http, _tokenManager)\n this.checkout = new CheckoutNamespace(_http)\n this.wallet = new BrowserWalletNamespace(_http)\n this.purchases = new BrowserPurchasesNamespace(_http)\n this.content = new BrowserContentNamespace(_http)\n }\n}\n","import type { TokenStorage, StoredTokens } from '@ledewire/core'\n\nconst DEFAULT_STORAGE_KEY = 'lw:tokens'\n\n/**\n * A {@link TokenStorage} adapter that persists tokens in `localStorage`.\n *\n * Tokens survive page reloads and browser restarts, which is convenient\n * for sites where users expect persistent sessions.\n *\n * **Security note:** `localStorage` is accessible to any JavaScript on the\n * same origin. Only use this adapter on sites you fully control and trust.\n * The default in-memory storage is safer for high-security use cases.\n *\n * @param key - The `localStorage` key used to store tokens.\n * Defaults to `'lw:tokens'`. Override this if you have multiple\n * LedeWire integrations on the same origin.\n *\n * @example\n * ```ts\n * import { init, localStorageAdapter } from '@ledewire/browser'\n *\n * const lw = init({\n * apiKey: 'your_api_key',\n * storage: localStorageAdapter(),\n * })\n * ```\n */\nexport function localStorageAdapter(key = DEFAULT_STORAGE_KEY): TokenStorage {\n return {\n getTokens(): StoredTokens | null {\n const raw = localStorage.getItem(key)\n if (!raw) return null\n try {\n return JSON.parse(raw) as StoredTokens\n } catch {\n // Corrupted value - treat as empty\n return null\n }\n },\n setTokens(tokens: StoredTokens): void {\n localStorage.setItem(key, JSON.stringify(tokens))\n },\n clearTokens(): void {\n localStorage.removeItem(key)\n },\n }\n}\n"],"names":["LedewireError","message","statusCode","code","__publicField","AuthError","ForbiddenError","NotFoundError","PurchaseError","DEFAULT_BASE_URL","HttpClient","config","path","params","body","url","method","isRetry","token","headers","response","REFRESH_THRESHOLD_MS","TokenManager","options","stored","refreshed","tokens","refreshToken","_err","MemoryTokenStorage","parseExpiresAt","expiresAt","BrowserAuthNamespace","http","tokenManager","res","BrowserConfigNamespace","BrowserContentNamespace","id","userId","BrowserPurchasesNamespace","BrowserWalletNamespace","sessionId","CheckoutNamespace","contentId","init","baseUrl","storage","data","BrowserClient","_http","_tokenManager","_config","DEFAULT_STORAGE_KEY","localStorageAdapter","key","raw"],"mappings":"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"}
1
+ {"version":3,"file":"ledewire.min.js","sources":["../../core/src/errors.ts","../../core/src/http-client.ts","../../core/src/token-manager.ts","../src/resources/auth.ts","../src/resources/config.ts","../src/resources/content.ts","../src/resources/purchases.ts","../src/resources/seller/content.ts","../src/resources/seller/index.ts","../src/resources/wallet.ts","../src/resources/checkout.ts","../src/client.ts","../src/local-storage-adapter.ts"],"sourcesContent":["/**\n * Base error class for all LedeWire SDK errors.\n * All errors thrown by the SDK are instances of this class,\n * making it safe to use `err instanceof LedewireError` as a type guard.\n *\n * @example\n * ```ts\n * try {\n * await client.purchases.create({ contentId: '...' })\n * } catch (err) {\n * if (err instanceof LedewireError) {\n * console.error(err.statusCode, err.message)\n * }\n * }\n * ```\n */\nexport class LedewireError extends Error {\n /** HTTP status code returned by the API (e.g. 400, 401, 404, 422). */\n public readonly statusCode: number\n\n /** Machine-readable error code from the API error body, if present. */\n public readonly code: number | undefined\n\n constructor(message: string, statusCode: number, code?: number) {\n super(message)\n this.name = 'LedewireError'\n this.statusCode = statusCode\n this.code = code\n // Restore prototype chain (required for extending built-in Error in TS)\n Object.setPrototypeOf(this, new.target.prototype)\n }\n}\n\n/**\n * Thrown when the request lacks valid authentication credentials,\n * or when a token refresh fails and re-authentication is required.\n */\nexport class AuthError extends LedewireError {\n constructor(message: string, code?: number) {\n super(message, 401, code)\n this.name = 'AuthError'\n }\n}\n\n/**\n * Thrown when the authenticated user does not have permission\n * to perform the requested operation.\n *\n * **Common cause — merchant login with the wrong account role:**\n * `POST /v1/auth/merchant/login/email` and `POST /v1/auth/merchant/login/google`\n * return `403 Forbidden` (not `401`) when the credentials are valid but the\n * account has no merchant store associations (e.g. a buyer account used on the\n * merchant endpoint). The `err.message` will be:\n * `\"This account does not have merchant access. Use a merchant or owner account.\"`\n *\n * Use a different account or ask a store owner to add your account as a member.\n *\n * @example\n * ```ts\n * import { ForbiddenError, AuthError } from '@ledewire/node'\n *\n * try {\n * await client.merchant.auth.loginWithGoogle({ id_token })\n * } catch (err) {\n * if (err instanceof ForbiddenError) {\n * // Credentials were valid but account has no merchant store access.\n * // err.message → \"This account does not have merchant access. Use a merchant or owner account.\"\n * console.error('Wrong account role:', err.message)\n * } else if (err instanceof AuthError) {\n * // Bad credentials or expired token — re-authenticate.\n * console.error('Authentication failed:', err.message)\n * }\n * }\n * ```\n */\nexport class ForbiddenError extends LedewireError {\n constructor(message: string, code?: number) {\n super(message, 403, code)\n this.name = 'ForbiddenError'\n }\n}\n\n/**\n * Thrown when the requested resource does not exist.\n */\nexport class NotFoundError extends LedewireError {\n constructor(message: string, code?: number) {\n super(message, 404, code)\n this.name = 'NotFoundError'\n }\n}\n\n/**\n * Thrown when the purchase cannot be completed due to a validation\n * failure, such as a price mismatch or a duplicate purchase.\n */\nexport class PurchaseError extends LedewireError {\n constructor(message: string, statusCode: number, code?: number) {\n super(message, statusCode, code)\n this.name = 'PurchaseError'\n }\n}\n","import type { components } from './api.gen.js'\nimport { AuthError, ForbiddenError, LedewireError, NotFoundError } from './errors.js'\n\n/** @internal */\ntype ApiErrorBody = components['schemas']['ErrorResponse']\n\n/**\n * Configuration options for `HttpClient`.\n */\nexport interface HttpClientConfig {\n /** API base URL. Defaults to the production API. */\n baseUrl?: string\n /**\n * Returns the current access token (or null) before each request.\n * If null, requests are sent without an Authorization header.\n */\n getAccessToken?: () => string | null | Promise<string | null>\n /**\n * Called when a 401 is received on the first attempt.\n * Should refresh and return the new access token, or null to propagate\n * an `AuthError` to the caller.\n */\n onUnauthorized?: () => string | null | Promise<string | null>\n}\n\nconst DEFAULT_BASE_URL = 'https://api.ledewire.com'\n\n/**\n * Core HTTP client used by all SDK packages.\n *\n * Features:\n * - Injects `Authorization: Bearer <token>` headers automatically\n * - Maps HTTP error responses to typed `LedewireError` subclasses\n * - On receiving a 401, calls `onUnauthorized` once and retries the request\n *\n * This is an internal class - consumers should use the\n * package-level client factories (`init` / `createClient`) instead.\n */\nexport class HttpClient {\n private readonly baseUrl: string\n private readonly getAccessToken: NonNullable<HttpClientConfig['getAccessToken']>\n private readonly onUnauthorized: NonNullable<HttpClientConfig['onUnauthorized']>\n\n constructor(config: HttpClientConfig = {}) {\n this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, '')\n this.getAccessToken = config.getAccessToken ?? (() => null)\n this.onUnauthorized = config.onUnauthorized ?? (() => null)\n }\n\n /**\n * GET request with optional query parameters.\n * @param path - API path (e.g. `/v1/wallet/balance`)\n * @param params - Query string parameters\n */\n async get<T>(path: string, params?: Record<string, string>): Promise<T> {\n return this.request<T>('GET', this.buildUrl(path, params))\n }\n\n /**\n * POST request.\n * @param path - API path\n * @param body - Request body (JSON-serialized)\n */\n async post<T>(path: string, body?: unknown): Promise<T> {\n return this.request<T>('POST', this.buildUrl(path), body)\n }\n\n /**\n * PUT request.\n * @param path - API path\n * @param body - Request body (JSON-serialized)\n */\n async put<T>(path: string, body?: unknown): Promise<T> {\n return this.request<T>('PUT', this.buildUrl(path), body)\n }\n\n /**\n * PATCH request.\n * @param path - API path\n * @param body - Partial update body (JSON-serialized)\n */\n async patch<T>(path: string, body?: unknown): Promise<T> {\n return this.request<T>('PATCH', this.buildUrl(path), body)\n }\n\n /**\n * DELETE request.\n * @param path - API path\n */\n async delete<T = void>(path: string): Promise<T> {\n return this.request<T>('DELETE', this.buildUrl(path))\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private buildUrl(path: string, params?: Record<string, string>): string {\n const url = `${this.baseUrl}${path}`\n if (!params || Object.keys(params).length === 0) return url\n return `${url}?${new URLSearchParams(params).toString()}`\n }\n\n private async request<T>(\n method: string,\n url: string,\n body?: unknown,\n isRetry = false,\n ): Promise<T> {\n const token = await this.getAccessToken()\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n }\n if (token) headers['Authorization'] = `Bearer ${token}`\n\n const response = await fetch(url, {\n method,\n headers,\n ...(body !== undefined && { body: JSON.stringify(body) }),\n })\n\n if (response.status === 401 && !isRetry) {\n const newToken = await this.onUnauthorized()\n if (newToken) {\n return this.request<T>(method, url, body, true)\n }\n throw new AuthError('Session expired. Please re-authenticate.')\n }\n\n if (!response.ok) {\n await this.throwApiError(response)\n }\n\n if (response.status === 204) {\n return undefined as T\n }\n\n return response.json() as Promise<T>\n }\n\n private async throwApiError(response: Response): Promise<never> {\n let body: ApiErrorBody | undefined\n try {\n body = (await response.json()) as ApiErrorBody\n } catch {\n // Non-JSON body - fall through to use statusText\n }\n\n const message = body?.error.message ?? response.statusText\n const code = body?.error.code\n\n switch (response.status) {\n case 401:\n throw new AuthError(message, code)\n case 403:\n throw new ForbiddenError(message, code)\n case 404:\n throw new NotFoundError(message, code)\n default:\n throw new LedewireError(message, response.status, code)\n }\n }\n}\n","import type { StoredTokens, TokenStorage } from './types.js'\nimport { AuthError } from './errors.js'\n\n/** How many milliseconds before expiry to proactively refresh the token. */\nconst REFRESH_THRESHOLD_MS = 60_000 // 60 seconds\n\n/**\n * Options for constructing a `TokenManager`.\n */\nexport interface TokenManagerOptions {\n storage: TokenStorage\n /**\n * Exchanges a refresh token for a new token pair.\n * Implemented by each package's client factory using the\n * `/v1/auth/token/refresh` endpoint.\n */\n refreshFn: (refreshToken: string) => Promise<StoredTokens>\n /**\n * Called after a successful background token refresh (not on initial login).\n * Use for side-effects only — e.g. audit logging, cache invalidation, or\n * notifying a secondary system when tokens rotate.\n *\n * **Do not use this for token persistence.** The `storage` adapter's\n * `setTokens` is the canonical persistence hook and is already called on\n * every refresh. Providing both will result in double-writes.\n *\n * @example\n * ```ts\n * onTokenRefreshed: async (tokens) => {\n * await auditLog.record('token_refreshed', { expiresAt: tokens.expiresAt })\n * }\n * ```\n */\n onTokenRefreshed?: (tokens: StoredTokens) => void | Promise<void>\n /**\n * Called when a refresh attempt fails and the user must re-authenticate.\n * Typically used to redirect to login or emit a UI event.\n *\n * @example\n * ```ts\n * onAuthExpired: () => { window.location.href = '/login' }\n * ```\n */\n onAuthExpired?: () => void | Promise<void>\n}\n\n/**\n * Manages the JWT token lifecycle transparently.\n *\n * - Proactively refreshes the access token when it expires within 60 seconds\n * - Handles reactive refresh on 401 responses from the HTTP client\n * - Deduplicates concurrent refresh calls (only one in-flight refresh at a time)\n * - Fires `onAuthExpired` when refresh fails so the UI can prompt re-login\n *\n * This is an internal class - use the package-level client factories instead.\n */\nexport class TokenManager {\n private readonly storage: TokenStorage\n private readonly refreshFn: TokenManagerOptions['refreshFn']\n private readonly onTokenRefreshed: TokenManagerOptions['onTokenRefreshed']\n private readonly onAuthExpired: TokenManagerOptions['onAuthExpired']\n private refreshPromise: Promise<StoredTokens | null> | null = null\n\n constructor(options: TokenManagerOptions) {\n this.storage = options.storage\n this.refreshFn = options.refreshFn\n this.onTokenRefreshed = options.onTokenRefreshed\n this.onAuthExpired = options.onAuthExpired\n }\n\n /**\n * Returns a valid access token, refreshing proactively if needed.\n * Returns `null` if no tokens are stored (user not authenticated).\n */\n async getAccessToken(): Promise<string | null> {\n const stored = await this.storage.getTokens()\n if (!stored) return null\n\n const expiresInMs = stored.expiresAt - Date.now()\n if (expiresInMs < REFRESH_THRESHOLD_MS) {\n const refreshed = await this.performRefresh(stored.refreshToken)\n return refreshed?.accessToken ?? null\n }\n\n return stored.accessToken\n }\n\n /**\n * Called by `HttpClient` when a 401 is received.\n * Attempts one refresh; returns the new access token or null.\n */\n async handleUnauthorized(): Promise<string | null> {\n const stored = await this.storage.getTokens()\n if (!stored) {\n await this.onAuthExpired?.()\n return null\n }\n const refreshed = await this.performRefresh(stored.refreshToken)\n if (!refreshed) await this.onAuthExpired?.()\n return refreshed?.accessToken ?? null\n }\n\n /** Store new tokens after a successful login or signup. */\n async setTokens(tokens: StoredTokens): Promise<void> {\n await this.storage.setTokens(tokens)\n }\n\n /** Clear all stored tokens (call on logout). */\n async clearTokens(): Promise<void> {\n await this.storage.clearTokens()\n }\n\n // ---------------------------------------------------------------------------\n // Private\n // ---------------------------------------------------------------------------\n\n private performRefresh(refreshToken: string): Promise<StoredTokens | null> {\n if (this.refreshPromise) return this.refreshPromise\n\n this.refreshPromise = this.refreshFn(refreshToken)\n .then(async (tokens) => {\n await this.storage.setTokens(tokens)\n await this.onTokenRefreshed?.(tokens)\n return tokens\n })\n .catch((_err: unknown) => {\n void this.storage.clearTokens()\n throw new AuthError('Token refresh failed. Please re-authenticate.')\n })\n .finally(() => {\n this.refreshPromise = null\n })\n\n return this.refreshPromise\n }\n}\n\n// ---------------------------------------------------------------------------\n// Built-in storage adapters\n// ---------------------------------------------------------------------------\n\n/**\n * In-memory token storage (default for both packages).\n * Tokens are cleared when the page unloads or the process exits.\n * This is the most secure default for browser environments.\n */\nexport class MemoryTokenStorage implements TokenStorage {\n private tokens: StoredTokens | null = null\n\n getTokens(): StoredTokens | null {\n return this.tokens\n }\n\n setTokens(tokens: StoredTokens): void {\n this.tokens = tokens\n }\n\n clearTokens(): void {\n this.tokens = null\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Converts an `expires_at` ISO 8601 string from an API auth response\n * into a Unix timestamp (milliseconds) for use in `StoredTokens.expiresAt`.\n *\n * @param expiresAt - ISO 8601 datetime string, e.g. `\"2026-01-01T12:00:00Z\"`\n * @returns Unix timestamp in milliseconds\n */\nexport function parseExpiresAt(expiresAt: string): number {\n return new Date(expiresAt).getTime()\n}\n","/**\n * Buyer authentication namespace for browser environments.\n *\n * Tokens are stored automatically in the `TokenManager` after every\n * successful call — callers never need to manage tokens directly.\n *\n * @module\n */\nimport { parseExpiresAt } from '@ledewire/core'\nimport type { HttpClient, TokenManager } from '@ledewire/core'\nimport type {\n AuthLoginApiKeyRequest,\n AuthLoginEmailRequest,\n AuthLoginOAuthRequest,\n AuthSignupRequest,\n AuthenticationResponse,\n} from '@ledewire/core'\n\n/**\n * Buyer authentication: signup, email/password login, Google OAuth.\n *\n * Obtain via `lw.auth` — do not construct directly.\n *\n * @example\n * ```ts\n * const lw = Ledewire.init({ apiKey: 'your_api_key' })\n * await lw.auth.loginWithEmail({ email: 'u@example.com', password: 'pw' })\n * ```\n */\nexport class BrowserAuthNamespace {\n /** @internal */\n constructor(\n private readonly http: HttpClient,\n private readonly tokenManager: TokenManager,\n ) {}\n\n /**\n * Register a new buyer account with email and password.\n * Tokens are stored automatically after successful signup.\n *\n * @param body - Signup credentials and display name.\n * @returns The authentication token response.\n */\n async signup(body: AuthSignupRequest): Promise<AuthenticationResponse> {\n const res = await this.http.post<AuthenticationResponse>('/v1/auth/signup', body)\n await this.storeTokens(res)\n return res\n }\n\n /**\n * Log in with email and password.\n * Tokens are stored automatically after successful login.\n *\n * @param body - Email and password credentials.\n * @returns The authentication token response.\n */\n async loginWithEmail(body: AuthLoginEmailRequest): Promise<AuthenticationResponse> {\n const res = await this.http.post<AuthenticationResponse>('/v1/auth/login/email', body)\n await this.storeTokens(res)\n return res\n }\n\n /**\n * Log in with a Google ID token obtained from the Google OAuth flow.\n * Tokens are stored automatically after successful login.\n *\n * @param body - The Google ID token.\n * @returns The authentication token response.\n */\n async loginWithGoogle(body: AuthLoginOAuthRequest): Promise<AuthenticationResponse> {\n const res = await this.http.post<AuthenticationResponse>('/v1/auth/login/google', body)\n await this.storeTokens(res)\n return res\n }\n\n /**\n * Log in using an API key to obtain a seller token.\n * Provide only `key` for read-only (`view`) access.\n * Provide both `key` and `secret` for read/write (`full`) access.\n * Tokens are stored automatically after successful authentication.\n *\n * Use this before calling `lw.seller.content.*` methods.\n *\n * @param body - API key credentials.\n * @returns The authentication token response.\n *\n * @example\n * ```ts\n * // View access (read-only) — sufficient for seller.content.list/search/get\n * await lw.auth.loginWithApiKey({ key: 'your_api_key' })\n *\n * // Full access (read/write)\n * await lw.auth.loginWithApiKey({ key: 'your_api_key', secret: 'your_secret' })\n * ```\n */\n async loginWithApiKey(body: AuthLoginApiKeyRequest): Promise<AuthenticationResponse> {\n const res = await this.http.post<AuthenticationResponse>('/v1/auth/login/api-key', body)\n await this.storeTokens(res)\n return res\n }\n\n private async storeTokens(res: AuthenticationResponse): Promise<void> {\n await this.tokenManager.setTokens({\n accessToken: res.access_token,\n refreshToken: res.refresh_token,\n expiresAt: parseExpiresAt(res.expires_at),\n })\n }\n}\n","/**\n * Platform configuration namespace (browser).\n *\n * Provides access to public (unauthenticated) platform-level configuration.\n *\n * @module\n */\nimport type { HttpClient } from '@ledewire/core'\nimport type { PublicConfigResponse } from '@ledewire/core'\n\n/**\n * Platform-level configuration for browser clients.\n *\n * Obtain via `client.config` — do not construct directly.\n */\nexport class BrowserConfigNamespace {\n /** @internal */\n constructor(protected readonly http: HttpClient) {}\n\n /**\n * Returns platform-level public configuration.\n * No authentication required — safe to call before the user has signed in.\n *\n * Use `google_client_id` to initialise the Google Identity Services library\n * on the storefront login page without requiring a prior authenticated call.\n *\n * @example\n * ```ts\n * const lw = init({ apiKey: 'your_api_key' })\n * const { google_client_id } = await lw.config.getPublic()\n * google.accounts.id.initialize({ client_id: google_client_id, callback })\n * ```\n */\n async getPublic(): Promise<PublicConfigResponse> {\n return this.http.get<PublicConfigResponse>('/v1/config/public')\n }\n}\n","import type { HttpClient } from '@ledewire/core'\nimport type { ContentWithAccessResponse } from '@ledewire/core'\n\n/**\n * Buyer content namespace — fetch content with real-time access information.\n *\n * Obtain via `lw.content` — do not construct directly.\n *\n * @example\n * ```ts\n * const { content, access } = await lw.content.getWithAccess('content-id')\n * if (access.has_access) showFullContent(content)\n * ```\n */\nexport class BrowserContentNamespace {\n constructor(protected readonly http: HttpClient) {}\n\n /**\n * Returns a content item together with access information for the\n * authenticated buyer (or a generic access response when unauthenticated).\n *\n * @param id - The content ID.\n * @param userId - Optional user ID to check a specific user's access status.\n * @returns The content item with access information.\n */\n async getWithAccess(id: string, userId?: string): Promise<ContentWithAccessResponse> {\n const params: Record<string, string> = {}\n if (userId !== undefined) {\n params['user_id'] = userId\n }\n return this.http.get<ContentWithAccessResponse>(\n `/v1/content/${id}/with-access`,\n Object.keys(params).length > 0 ? params : undefined,\n )\n }\n}\n","import type { HttpClient } from '@ledewire/core'\nimport type { PurchaseCreateRequest, PurchaseResponse } from '@ledewire/core'\n\n/**\n * Buyer purchases namespace — create and retrieve content purchases.\n *\n * Obtain via `lw.purchases` — do not construct directly.\n *\n * @example\n * ```ts\n * await lw.purchases.create({ content_id: 'content-id' })\n * const all = await lw.purchases.list()\n * ```\n */\nexport class BrowserPurchasesNamespace {\n constructor(protected readonly http: HttpClient) {}\n\n /**\n * Completes a content purchase using the buyer's wallet balance.\n *\n * @param body - The content ID and expected price in cents.\n * @returns The completed purchase record.\n */\n async create(body: PurchaseCreateRequest): Promise<PurchaseResponse> {\n return this.http.post<PurchaseResponse>('/v1/purchases', body)\n }\n\n /**\n * Returns all purchases made by the authenticated buyer.\n *\n * @returns A list of purchase records, newest first.\n */\n async list(): Promise<PurchaseResponse[]> {\n return this.http.get<PurchaseResponse[]>('/v1/purchases')\n }\n\n /**\n * Returns a single purchase by ID.\n *\n * @param id - The purchase ID.\n * @returns The purchase record.\n */\n async get(id: string): Promise<PurchaseResponse> {\n return this.http.get<PurchaseResponse>(`/v1/purchases/${id}`)\n }\n}\n","/**\n * Seller content namespace for browser environments.\n *\n * Read-only operations using a view-permission API key token.\n * Hits the `/v1/seller/content` family of endpoints — store identity is\n * derived server-side from the token, so no `store_id` is required.\n *\n * @module\n */\nimport type { HttpClient } from '@ledewire/core'\nimport type { ContentResponse } from '@ledewire/core'\n\n/**\n * Search criteria for seller content search.\n * At least one field must be supplied. All supplied fields must match (AND logic).\n */\nexport interface SellerContentSearchRequest {\n /** Case-insensitive partial match against the content title. */\n title?: string\n /** Case-insensitive partial match against the content URI (`external_ref` content only). */\n uri?: string\n /** Exact key/value pairs to AND-match against content metadata. */\n metadata?: Record<string, unknown>\n}\n\n/**\n * Seller content namespace — list, search, and fetch store content by API key.\n *\n * Obtain via `lw.seller.content` — do not construct directly.\n *\n * @example\n * ```ts\n * const lw = Ledewire.init({ apiKey: 'your_api_key' })\n * await lw.auth.loginWithApiKey({ key: 'your_api_key' })\n *\n * const items = await lw.seller.content.list()\n * const results = await lw.seller.content.search({ title: 'intro' })\n * const item = await lw.seller.content.get('content-id')\n * ```\n */\nexport class BrowserSellerContentNamespace {\n /** @internal */\n constructor(private readonly http: HttpClient) {}\n\n /**\n * List all content for the authenticated seller's store.\n * Requires a token with at least `view` permission.\n *\n * @returns Array of content items.\n *\n * @example\n * ```ts\n * const items = await lw.seller.content.list()\n * ```\n */\n async list(): Promise<ContentResponse[]> {\n return this.http.get<ContentResponse[]>('/v1/seller/content')\n }\n\n /**\n * Search content by title, URI, and/or metadata (AND logic).\n * At least one criterion must be supplied.\n * Requires a token with at least `view` permission.\n *\n * @param body - Search criteria (title, uri, and/or metadata).\n * @returns Array of matching content items.\n *\n * @example\n * ```ts\n * const results = await lw.seller.content.search({ title: 'intro' })\n * const byUri = await lw.seller.content.search({ uri: 'vimeo.com' })\n * const byMeta = await lw.seller.content.search({ metadata: { author: 'Alice' } })\n * ```\n */\n async search(body: SellerContentSearchRequest): Promise<ContentResponse[]> {\n return this.http.post<ContentResponse[]>('/v1/seller/content/search', body)\n }\n\n /**\n * Get a single content item by ID.\n * Requires a token with at least `view` permission.\n *\n * @param id - The content ID.\n * @returns The content item.\n *\n * @example\n * ```ts\n * const item = await lw.seller.content.get('content-id')\n * ```\n */\n async get(id: string): Promise<ContentResponse> {\n return this.http.get<ContentResponse>(`/v1/seller/content/${id}`)\n }\n}\n","/**\n * Browser seller namespace — API-key-authenticated read operations.\n *\n * @module\n */\nimport type { HttpClient } from '@ledewire/core'\nimport { BrowserSellerContentNamespace } from './content.js'\n\n/**\n * Seller operations accessible with a view-permission API key token.\n *\n * Obtain via `lw.seller` — do not construct directly.\n *\n * @example\n * ```ts\n * const lw = Ledewire.init({ apiKey: 'your_api_key' })\n * await lw.auth.loginWithApiKey({ key: 'your_api_key' })\n * const items = await lw.seller.content.list()\n * ```\n */\nexport class BrowserSellerNamespace {\n /** Seller content: list, search, and get by API key. */\n readonly content: BrowserSellerContentNamespace\n\n /** @internal */\n constructor(http: HttpClient) {\n this.content = new BrowserSellerContentNamespace(http)\n }\n}\n","import type { HttpClient } from '@ledewire/core'\nimport type {\n WalletBalanceResponse,\n WalletPaymentSessionRequest,\n WalletPaymentSessionResponse,\n WalletPaymentStatusResponse,\n WalletTransactionItem,\n} from '@ledewire/core'\n\n/**\n * Buyer wallet namespace — balance, transactions, and payment session management.\n *\n * Obtain via `lw.wallet` — do not construct directly.\n *\n * @example\n * ```ts\n * const { balance_cents } = await lw.wallet.balance()\n * const session = await lw.wallet.createPaymentSession({ amount_cents: 500 })\n * ```\n */\nexport class BrowserWalletNamespace {\n constructor(protected readonly http: HttpClient) {}\n\n /**\n * Returns the authenticated buyer's current wallet balance.\n *\n * @returns The current wallet balance in cents.\n */\n async balance(): Promise<WalletBalanceResponse> {\n return this.http.get<WalletBalanceResponse>('/v1/wallet/balance')\n }\n\n /**\n * Returns the authenticated buyer's wallet transaction history, newest first.\n *\n * @returns A list of completed wallet transaction entries.\n */\n async transactions(): Promise<WalletTransactionItem[]> {\n return this.http.get<WalletTransactionItem[]>('/v1/wallet/transactions')\n }\n\n /**\n * Creates a payment session for funding the buyer's wallet.\n *\n * @param body - The amount and currency to fund.\n * @returns Payment session details for use with the payment provider widget.\n */\n async createPaymentSession(\n body: WalletPaymentSessionRequest,\n ): Promise<WalletPaymentSessionResponse> {\n return this.http.post<WalletPaymentSessionResponse>('/v1/wallet/payment-session', body)\n }\n\n /**\n * Polls the status of a wallet funding payment session.\n *\n * @param sessionId - The session ID returned by `createPaymentSession`.\n * @returns The current payment status.\n */\n async getPaymentStatus(sessionId: string): Promise<WalletPaymentStatusResponse> {\n return this.http.get<WalletPaymentStatusResponse>(`/v1/wallet/payment-status/${sessionId}`)\n }\n}\n","import type { HttpClient } from '@ledewire/core'\nimport type { CheckoutStateResponse } from '@ledewire/core'\n\n/**\n * Checkout namespace — buyer checkout state for a specific content item.\n *\n * Obtain via `lw.checkout` — do not construct directly.\n *\n * @example\n * ```ts\n * const state = await lw.checkout.state('content-id')\n * // state.checkout_state.next_required_action:\n * // 'authenticate' | 'fund_wallet' | 'purchase' | 'view_content'\n * ```\n */\nexport class CheckoutNamespace {\n constructor(protected readonly http: HttpClient) {}\n\n /**\n * Returns the authenticated buyer's checkout readiness for a given piece of\n * content. Indicates whether the buyer needs to authenticate, fund their\n * wallet, or purchase before accessing the content.\n *\n * Works without authentication — returns anonymous checkout state when the\n * buyer is not logged in.\n *\n * @param contentId - The content item ID.\n * @returns The checkout state, including what action is required next.\n */\n async state(contentId: string): Promise<CheckoutStateResponse> {\n return this.http.get<CheckoutStateResponse>(`/v1/checkout/state/${contentId}`)\n }\n}\n","import { HttpClient, MemoryTokenStorage, TokenManager, parseExpiresAt } from '@ledewire/core'\nimport type { TokenStorage } from '@ledewire/core'\nimport { BrowserAuthNamespace } from './resources/auth.js'\nimport { BrowserConfigNamespace } from './resources/config.js'\nimport { BrowserContentNamespace } from './resources/content.js'\nimport { BrowserPurchasesNamespace } from './resources/purchases.js'\nimport { BrowserSellerNamespace } from './resources/seller/index.js'\nimport { BrowserWalletNamespace } from './resources/wallet.js'\nimport { CheckoutNamespace } from './resources/checkout.js'\n\n/**\n * Configuration options for the LedeWire browser client.\n */\nexport interface BrowserClientConfig {\n /**\n * LedeWire API key that identifies the store.\n * Obtained from the LedeWire merchant dashboard.\n */\n apiKey: string\n /**\n * Override the API base URL.\n * Defaults to `https://api.ledewire.com`.\n */\n baseUrl?: string\n /**\n * Token storage adapter.\n * Defaults to {@link MemoryTokenStorage} (in-memory, most secure).\n *\n * To persist sessions across page reloads, use the built-in\n * `localStorageAdapter`:\n * ```ts\n * import { init, localStorageAdapter } from '@ledewire/browser'\n * const lw = init({ apiKey: '...', storage: localStorageAdapter() })\n * ```\n */\n storage?: TokenStorage\n /**\n * Called when the user's session expires and cannot be refreshed.\n * Use this to show a re-authentication prompt.\n *\n * @example\n * ```ts\n * onAuthExpired: () => showLoginModal()\n * ```\n */\n onAuthExpired?: () => void\n}\n\n/**\n * Initialises a LedeWire browser client.\n * This is the primary entry point for the browser SDK.\n *\n * @example CDN usage:\n * ```html\n * <script src=\"https://cdn.jsdelivr.net/npm/@ledewire/browser@1/dist/ledewire.min.js\"></script>\n * <script>\n * const lw = Ledewire.init({ apiKey: 'your_api_key' })\n * const state = await lw.checkout.state('content-id')\n * </script>\n * ```\n *\n * @example npm usage:\n * ```ts\n * import { init } from '@ledewire/browser'\n * const lw = init({ apiKey: 'your_api_key' })\n * ```\n */\nexport function init(config: BrowserClientConfig): BrowserClient {\n const baseUrl = config.baseUrl ?? 'https://api.ledewire.com'\n const storage = config.storage ?? new MemoryTokenStorage()\n\n const tokenManager = new TokenManager({\n storage,\n refreshFn: async (refreshToken) => {\n const res = await fetch(`${baseUrl}/v1/auth/token/refresh`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ refresh_token: refreshToken }),\n })\n if (!res.ok) throw new Error('Token refresh failed')\n const data = (await res.json()) as {\n access_token: string\n refresh_token: string\n expires_at: string\n }\n return {\n accessToken: data.access_token,\n refreshToken: data.refresh_token,\n expiresAt: parseExpiresAt(data.expires_at),\n }\n },\n ...(config.onAuthExpired !== undefined && { onAuthExpired: config.onAuthExpired }),\n })\n\n const http = new HttpClient({\n baseUrl,\n getAccessToken: () => tokenManager.getAccessToken(),\n onUnauthorized: () => tokenManager.handleUnauthorized(),\n })\n\n return new BrowserClient(http, tokenManager, config)\n}\n\n/**\n * The LedeWire browser client.\n * Access buyer flows through the namespaced properties.\n *\n * @remarks\n * Instantiate with {@link init} rather than constructing directly.\n */\nexport class BrowserClient {\n /** Platform-level public configuration (no auth required) */\n readonly config: BrowserConfigNamespace\n\n /** Buyer authentication: email/password signup/login, Google, password reset */\n readonly auth: BrowserAuthNamespace\n\n /** Checkout state machine: determines next action for a piece of content */\n readonly checkout: CheckoutNamespace\n\n /** Wallet: balance, fund via payment session, transaction history */\n readonly wallet: BrowserWalletNamespace\n\n /** Content purchases for the authenticated buyer */\n readonly purchases: BrowserPurchasesNamespace\n\n /** Public content with per-user access information */\n readonly content: BrowserContentNamespace\n\n /** Seller content: list, search, and get (requires API key view token) */\n readonly seller: BrowserSellerNamespace\n\n /** @internal */\n constructor(\n public readonly _http: HttpClient,\n public readonly _tokenManager: TokenManager,\n public readonly _config: BrowserClientConfig,\n ) {\n this.config = new BrowserConfigNamespace(_http)\n this.auth = new BrowserAuthNamespace(_http, _tokenManager)\n this.checkout = new CheckoutNamespace(_http)\n this.wallet = new BrowserWalletNamespace(_http)\n this.purchases = new BrowserPurchasesNamespace(_http)\n this.content = new BrowserContentNamespace(_http)\n this.seller = new BrowserSellerNamespace(_http)\n }\n}\n","import type { TokenStorage, StoredTokens } from '@ledewire/core'\n\nconst DEFAULT_STORAGE_KEY = 'lw:tokens'\n\n/**\n * A {@link TokenStorage} adapter that persists tokens in `localStorage`.\n *\n * Tokens survive page reloads and browser restarts, which is convenient\n * for sites where users expect persistent sessions.\n *\n * **Security note:** `localStorage` is accessible to any JavaScript on the\n * same origin. Only use this adapter on sites you fully control and trust.\n * The default in-memory storage is safer for high-security use cases.\n *\n * @param key - The `localStorage` key used to store tokens.\n * Defaults to `'lw:tokens'`. Override this if you have multiple\n * LedeWire integrations on the same origin.\n *\n * @example\n * ```ts\n * import { init, localStorageAdapter } from '@ledewire/browser'\n *\n * const lw = init({\n * apiKey: 'your_api_key',\n * storage: localStorageAdapter(),\n * })\n * ```\n */\nexport function localStorageAdapter(key = DEFAULT_STORAGE_KEY): TokenStorage {\n return {\n getTokens(): StoredTokens | null {\n const raw = localStorage.getItem(key)\n if (!raw) return null\n try {\n return JSON.parse(raw) as StoredTokens\n } catch {\n // Corrupted value - treat as empty\n return null\n }\n },\n setTokens(tokens: StoredTokens): void {\n localStorage.setItem(key, JSON.stringify(tokens))\n },\n clearTokens(): void {\n localStorage.removeItem(key)\n },\n }\n}\n"],"names":["LedewireError","message","statusCode","code","__publicField","AuthError","ForbiddenError","NotFoundError","PurchaseError","DEFAULT_BASE_URL","HttpClient","config","path","params","body","url","method","isRetry","token","headers","response","REFRESH_THRESHOLD_MS","TokenManager","options","stored","refreshed","tokens","refreshToken","_err","MemoryTokenStorage","parseExpiresAt","expiresAt","BrowserAuthNamespace","http","tokenManager","res","BrowserConfigNamespace","BrowserContentNamespace","id","userId","BrowserPurchasesNamespace","BrowserSellerContentNamespace","BrowserSellerNamespace","BrowserWalletNamespace","sessionId","CheckoutNamespace","contentId","init","baseUrl","storage","data","BrowserClient","_http","_tokenManager","_config","DEFAULT_STORAGE_KEY","localStorageAdapter","key","raw"],"mappings":"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"}
package/llms.txt ADDED
@@ -0,0 +1,290 @@
1
+ # @ledewire/browser
2
+
3
+ > LedeWire Browser SDK — buyer authentication, checkout, wallet, and purchases for web apps.
4
+ > Works as a CDN script tag (no build step) or as an ESM npm package.
5
+ > npm install @ledewire/browser
6
+ > Source: https://github.com/ledewire/ledewire-js-sdk
7
+ > API docs: https://ledewire.github.io/ledewire-js-sdk/api/
8
+
9
+ ## CDN (no install required)
10
+
11
+ ```html
12
+ <!-- Pin to a major version in production -->
13
+ <script src="https://cdn.jsdelivr.net/npm/@ledewire/browser@0/dist/ledewire.min.js"></script>
14
+ <script>
15
+ const lw = Ledewire.init({ apiKey: 'your_api_key' })
16
+ </script>
17
+ ```
18
+
19
+ The global is `Ledewire`. All methods return Promises — use async/await or `.then()`.
20
+
21
+ ## npm / ESM install
22
+
23
+ ```bash
24
+ npm install @ledewire/browser
25
+ ```
26
+
27
+ ```ts
28
+ import { init, localStorageAdapter } from '@ledewire/browser'
29
+ ```
30
+
31
+ ## init() configuration
32
+
33
+ ```ts
34
+ import { init } from '@ledewire/browser'
35
+ import type { BrowserClientConfig } from '@ledewire/browser'
36
+
37
+ const lw = init({
38
+ apiKey: string, // required — your LedeWire platform API key
39
+
40
+ baseUrl?: string, // defaults to 'https://api.ledewire.com'
41
+
42
+ // Token persistence — defaults to MemoryTokenStorage (cleared on page unload)
43
+ storage?: TokenStorage,
44
+
45
+ onTokenRefreshed?: (tokens: StoredTokens) => void,
46
+ onAuthExpired?: () => void, // called when refresh fails — show login UI
47
+ })
48
+ ```
49
+
50
+ Token refresh is automatic — never call a refresh method manually.
51
+
52
+ ## localStorageAdapter
53
+
54
+ Persist sessions across page reloads using `localStorage`:
55
+
56
+ ```ts
57
+ import { init, localStorageAdapter } from '@ledewire/browser'
58
+
59
+ const lw = init({
60
+ apiKey: 'your_api_key',
61
+ storage: localStorageAdapter(), // persists under 'lw_tokens' key
62
+ onAuthExpired: () => showLoginModal(),
63
+ })
64
+ ```
65
+
66
+ Use `MemoryTokenStorage` (default) for highest security — tokens are never written
67
+ to disk and are cleared when the page unloads.
68
+
69
+ ## Field naming convention
70
+
71
+ All API response types use `snake_case` field names (`access_token`, `price_cents`,
72
+ `content_uri`), matching the wire format exactly.
73
+
74
+ SDK-owned types use `camelCase` where they do not map 1:1 to a raw API response
75
+ field: `StoredTokens.accessToken`, `StoredTokens.expiresAt: number` (a converted
76
+ Unix ms timestamp, not the raw `expires_at: string`).
77
+
78
+ Do not attempt to remap snake_case fields — the SDK has no `transformKeys` option.
79
+
80
+ ## Complete method reference
81
+
82
+ ### lw.config
83
+
84
+ ```ts
85
+ lw.config.getPublic() → Promise<PublicConfigResponse>
86
+ // { google_client_id: string, ... }
87
+ // Safe to call before authentication — use to get the Google Client ID for sign-in UI.
88
+ ```
89
+
90
+ ### lw.auth (buyer and seller auth)
91
+
92
+ ```ts
93
+ lw.auth.signup(body) → Promise<AuthenticationResponse>
94
+ lw.auth.loginWithEmail({ email, password }) → Promise<AuthenticationResponse>
95
+ lw.auth.loginWithGoogle({ id_token }) → Promise<AuthenticationResponse>
96
+ lw.auth.loginWithApiKey({ key, secret? }) → Promise<AuthenticationResponse>
97
+ // key-only → view permission (read-only, sufficient for lw.seller.content.*)
98
+ // key + secret → full permission (read/write)
99
+ lw.auth.logout() → Promise<void>
100
+ lw.auth.requestPasswordReset({ email }) → Promise<void>
101
+ lw.auth.resetPassword({ token, password }) → Promise<void>
102
+ ```
103
+
104
+ Tokens are stored automatically after every successful login.
105
+
106
+ ### lw.checkout
107
+
108
+ ```ts
109
+ lw.checkout.state(contentId: string) → Promise<CheckoutStateResponse>
110
+ ```
111
+
112
+ Use this to determine what the current visitor needs to do before accessing content:
113
+
114
+ ```ts
115
+ const { checkout_state } = await lw.checkout.state('article-id')
116
+
117
+ switch (checkout_state.next_required_action) {
118
+ case 'authenticate':
119
+ // Show login/signup UI
120
+ break
121
+ case 'fund_wallet':
122
+ // Show wallet funding UI (checkout_state.has_sufficient_funds === false)
123
+ break
124
+ case 'purchase':
125
+ // Show purchase confirmation UI
126
+ break
127
+ case 'view_content':
128
+ // User has access — fetch and render the content
129
+ break
130
+ }
131
+ ```
132
+
133
+ ### lw.wallet
134
+
135
+ ```ts
136
+ lw.wallet.balance() → Promise<WalletBalanceResponse>
137
+ // { balance_cents: number }
138
+
139
+ lw.wallet.transactions(params?: PaginationParams) → Promise<{ data: WalletTransactionItem[], pagination: PaginationMeta }>
140
+
141
+ lw.wallet.createPaymentSession({ amount_cents: number }) → Promise<WalletPaymentSessionResponse>
142
+ // { payment_url: string, session_id: string, ... } — redirect user to payment_url
143
+
144
+ lw.wallet.getPaymentStatus(sessionId: string) → Promise<WalletPaymentStatusResponse>
145
+ // Poll after return from payment redirect to confirm funding
146
+ ```
147
+
148
+ ### lw.purchases
149
+
150
+ ```ts
151
+ lw.purchases.create({ content_id: string }) → Promise<PurchaseResponse>
152
+ lw.purchases.list(params?: PaginationParams) → Promise<{ data: PurchaseResponse[], pagination: PaginationMeta }>
153
+ lw.purchases.get(purchaseId: string) → Promise<PurchaseResponse>
154
+ ```
155
+
156
+ ### lw.content
157
+
158
+ ```ts
159
+ lw.content.getWithAccess(contentId: string, options?) → Promise<ContentWithAccessResponse>
160
+ // Returns content payload + access info. Requires a valid buyer session.
161
+ // content_type: 'markdown' | 'external_ref'
162
+ // content_body: base64-encoded string (use atob() to decode markdown)
163
+ // content_uri: URL for external_ref content (Vimeo, YouTube, PDF, etc.)
164
+ ```
165
+
166
+ ### lw.seller.content (requires API key login)
167
+
168
+ Read-only operations scoped to the authenticated seller's store.
169
+ Store identity is derived server-side from the token — no `store_id` needed.
170
+
171
+ ```ts
172
+ // 1. Obtain a view token (key-only is sufficient)
173
+ await lw.auth.loginWithApiKey({ key: 'your_api_key' })
174
+
175
+ // 2. List all store content
176
+ lw.seller.content.list() → Promise<ContentResponse[]>
177
+
178
+ // 3. Search by title, URI, or metadata (AND logic — at least one field required)
179
+ lw.seller.content.search({ title?: string, uri?: string, metadata?: Record<string, unknown> })
180
+ → Promise<ContentResponse[]>
181
+
182
+ // 4. Fetch a single item
183
+ lw.seller.content.get(contentId: string) → Promise<ContentResponse>
184
+ ```
185
+
186
+ ## Full checkout flow example
187
+
188
+ ```ts
189
+ const lw = Ledewire.init({ apiKey: 'your_api_key' })
190
+
191
+ const { checkout_state } = await lw.checkout.state('article-123')
192
+
193
+ switch (checkout_state.next_required_action) {
194
+ case 'authenticate':
195
+ await lw.auth.loginWithEmail({ email, password })
196
+ break
197
+
198
+ case 'fund_wallet':
199
+ const session = await lw.wallet.createPaymentSession({ amount_cents: 500 })
200
+ window.location.href = session.payment_url // Stripe checkout
201
+ break
202
+
203
+ case 'purchase':
204
+ await lw.purchases.create({ content_id: 'article-123' })
205
+ break
206
+
207
+ case 'view_content':
208
+ const { content_type, content_body, content_uri } =
209
+ await lw.content.getWithAccess('article-123')
210
+ if (content_type === 'markdown') {
211
+ renderMarkdown(atob(content_body)) // base64-decode then render
212
+ } else {
213
+ window.location.href = content_uri // redirect to gated video/PDF
214
+ }
215
+ break
216
+ }
217
+ ```
218
+
219
+ ## Google OAuth sign-in setup
220
+
221
+ ```ts
222
+ // Fetch the platform's Google Client ID before the user signs in:
223
+ const { google_client_id } = await lw.config.getPublic()
224
+ google.accounts.id.initialize({
225
+ client_id: google_client_id,
226
+ callback: async ({ credential }) => {
227
+ await lw.auth.loginWithGoogle({ id_token: credential })
228
+ },
229
+ })
230
+ google.accounts.id.renderButton(document.getElementById('signin-btn'), { theme: 'outline' })
231
+ ```
232
+
233
+ ## Key types
234
+
235
+ ```ts
236
+ interface CheckoutStateResponse {
237
+ content_id: string
238
+ content_title: string
239
+ price_cents: number
240
+ checkout_state: {
241
+ is_authenticated: boolean
242
+ has_sufficient_funds: boolean | null // null when not authenticated
243
+ has_purchased: boolean
244
+ next_required_action: 'authenticate' | 'fund_wallet' | 'purchase' | 'view_content'
245
+ }
246
+ }
247
+
248
+ interface StoredTokens {
249
+ accessToken: string
250
+ refreshToken: string
251
+ expiresAt: number // Unix timestamp (ms)
252
+ }
253
+
254
+ interface TokenStorage {
255
+ getTokens(): Promise<StoredTokens | null>
256
+ setTokens(tokens: StoredTokens): Promise<void>
257
+ clearTokens(): Promise<void>
258
+ }
259
+ ```
260
+
261
+ ## Error classes
262
+
263
+ All errors extend `LedewireError`. Import named subclasses for actionable handling:
264
+
265
+ ```ts
266
+ import { LedewireError, AuthError, ForbiddenError, NotFoundError, PurchaseError } from '@ledewire/browser'
267
+
268
+ class LedewireError extends Error {
269
+ statusCode: number // HTTP status code
270
+ code: number | undefined // machine-readable code from API body
271
+ message: string // human-readable description from API body
272
+ }
273
+
274
+ class AuthError extends LedewireError {} // 401 — bad credentials / session expired
275
+ class ForbiddenError extends LedewireError {} // 403 — authenticated but access denied
276
+ class NotFoundError extends LedewireError {} // 404 — resource not found
277
+ class PurchaseError extends LedewireError {} // 409/422 — purchase validation failure
278
+ ```
279
+
280
+ ```ts
281
+ try {
282
+ await lw.purchases.create({ content_id: 'article-123' })
283
+ } catch (err) {
284
+ if (err instanceof AuthError) {
285
+ showLoginModal()
286
+ } else if (err instanceof PurchaseError) {
287
+ showError('Purchase failed: ' + err.message)
288
+ }
289
+ }
290
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ledewire/browser",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "LedeWire SDK for browsers — embed a paywall with a single script tag",
5
5
  "type": "module",
6
6
  "exports": {
@@ -15,7 +15,8 @@
15
15
  "types": "./dist/index.d.ts",
16
16
  "files": [
17
17
  "dist",
18
- "README.md"
18
+ "README.md",
19
+ "llms.txt"
19
20
  ],
20
21
  "keywords": [
21
22
  "ledewire",